From f7ef8a14bd76983bb932a6a391e6768f1ed9af10 Mon Sep 17 00:00:00 2001 From: Hanno Witzleb <48943886+Xipit@users.noreply.github.com> Date: Wed, 15 Apr 2026 16:36:25 +0200 Subject: [PATCH] Add Feature to disable snapping to buildplate (#11801) * identified code for snapping to buidlplate * rename internal name to ensure_on_bed to be consistent, saves option in 2mf, finish Move UI, use in both ensure_on_bed() functions * makes auto_drop a per-object setting, removes global setting * remove adUndef, add auto_drop to constructor/serialize * fixes drop() button * add "auto_drop" checkmark to "load as single object" dialog, nothing changes if auto_drop == yes || "load as single object", if auto_drop == false and "load as single object" == false the objects now retain their relative position to each other * retains auto_drop (and printable) state when assembling or splitting objects, adds ObjectList::printable_state_changed() overload to be able to only provide ModelObject* vector * adds dialog when splitting to ask if auto_drop should be disabled, only shows when auto_drop enabled and atleast one volume floating * adds arrow indicator on bounding box if auto_drop == false * removes unneeded code, keeps "auto_drop" naming consistent * makes for loop simpler in set_printable, set_auto_drop and get_auto_drop, makes get_auto_drop const, fixes wording in Snapshot text --------- Co-authored-by: Hanno Witzleb Co-authored-by: SoftFever Co-authored-by: Ian Bassi --- src/libslic3r/Format/bbs_3mf.cpp | 20 +- src/libslic3r/Model.cpp | 12 +- src/libslic3r/Model.hpp | 10 +- src/slic3r/GUI/GLCanvas3D.cpp | 197 ++++++++++------- src/slic3r/GUI/GUI_Factories.cpp | 54 ++++- src/slic3r/GUI/GUI_Factories.hpp | 2 + src/slic3r/GUI/GUI_ObjectList.cpp | 85 +++++++- src/slic3r/GUI/GUI_ObjectList.hpp | 2 + src/slic3r/GUI/GUI_ObjectTable.cpp | 7 +- src/slic3r/GUI/ObjectDataViewModel.cpp | 4 +- src/slic3r/GUI/ObjectDataViewModel.hpp | 10 +- src/slic3r/GUI/Plater.cpp | 102 +++++++-- src/slic3r/GUI/Plater.hpp | 3 +- src/slic3r/GUI/Selection.cpp | 281 ++++++++++++++++++------- src/slic3r/GUI/Selection.hpp | 7 +- 15 files changed, 583 insertions(+), 213 deletions(-) diff --git a/src/libslic3r/Format/bbs_3mf.cpp b/src/libslic3r/Format/bbs_3mf.cpp index dcbf638483..42f55aae8a 100644 --- a/src/libslic3r/Format/bbs_3mf.cpp +++ b/src/libslic3r/Format/bbs_3mf.cpp @@ -313,6 +313,7 @@ static constexpr const char* TRANSFORM_ATTR = "transform"; // BBS static constexpr const char* OFFSET_ATTR = "offset"; static constexpr const char* PRINTABLE_ATTR = "printable"; +static constexpr const char* AUTO_DROP_ATTR = "auto_drop"; static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count"; static constexpr const char* CUSTOM_SUPPORTS_ATTR = "paint_supports"; static constexpr const char* CUSTOM_FUZZY_SKIN_ATTR = "paint_fuzzy_skin"; @@ -1204,7 +1205,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) bool _handle_start_text_configuration(const char** attributes, unsigned int num_attributes); bool _handle_start_shape_configuration(const char **attributes, unsigned int num_attributes); - bool _create_object_instance(std::string const & path, int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter); + bool _create_object_instance(std::string const & path, int object_id, const Transform3d& transform, const bool printable, const bool auto_drop, unsigned int recur_counter); void _apply_transform(ModelInstance& instance, const Transform3d& transform); @@ -3766,8 +3767,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) std::string path = bbs_get_attribute_value_string(attributes, num_attributes, PPATH_ATTR); Transform3d transform = bbs_get_transform_from_3mf_specs_string(bbs_get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); int printable = bbs_get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR); + int auto_drop = bbs_get_attribute_value_bool(attributes, num_attributes, AUTO_DROP_ATTR); - return !m_load_model || _create_object_instance(path, object_id, transform, printable, 1); + return !m_load_model || _create_object_instance(path, object_id, transform, printable, auto_drop, 1); } bool _BBS_3MF_Importer::_handle_end_item() @@ -3989,7 +3991,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) return true; } - bool _BBS_3MF_Importer::_create_object_instance(std::string const & path, int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter) + bool _BBS_3MF_Importer::_create_object_instance(std::string const & path, int object_id, const Transform3d& transform, const bool printable, const bool auto_drop, unsigned int recur_counter) { static const unsigned int MAX_RECURSIONS = 10; @@ -4026,6 +4028,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) return false; } instance->printable = printable; + instance->auto_drop = auto_drop; m_instances.emplace_back(instance, transform); @@ -4058,6 +4061,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) return false; } instance->printable = printable; + instance->auto_drop = auto_drop; m_instances.emplace_back(instance, transform); } @@ -5614,12 +5618,14 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) unsigned int id; Transform3d transform; bool printable; + bool auto_drop; - BuildItem(std::string const & path, unsigned int id, const Transform3d& transform, const bool printable) + BuildItem(std::string const & path, unsigned int id, const Transform3d& transform, const bool printable, const bool auto_drop) : path(path) , id(id) , transform(transform) , printable(printable) + , auto_drop(auto_drop) { } }; @@ -6784,7 +6790,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) Transform3d t = instance->get_matrix(); // instance_id is just a 1 indexed index in build_items. //assert(m_skip_static || curr_id == build_items.size() + 1); - build_items.emplace_back("", object_it->second.object_id, t, instance->printable); + + build_items.emplace_back("", object_it->second.object_id, t, instance->printable, instance->auto_drop); count++; } @@ -7220,7 +7227,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) stream << "\" " << PPATH_ATTR << "=\"" << xml_escape(item.path); stream << "\" " << TRANSFORM_ATTR << "=\""; add_transformation(stream, item.transform); - stream << "\" " << PRINTABLE_ATTR << "=\"" << item.printable << "\"/>\n"; + stream << "\" " << PRINTABLE_ATTR << "=\"" << item.printable; + stream << "\" " << AUTO_DROP_ATTR << "=\"" << item.auto_drop << "\"/>\n"; } stream << " \n"; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 4ef737de33..05a56c3a47 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -14,6 +14,7 @@ #include "Format/AMF.hpp" #include "Format/svg.hpp" +#include "Format/bbs_3mf.hpp" #include "Format/DRC.hpp" // BBS #include "FaceDetector.hpp" @@ -1088,7 +1089,6 @@ bool Model::is_fuzzy_skin_painted() const return std::any_of(this->objects.cbegin(), this->objects.cend(), [](const ModelObject *mo) { return mo->is_fuzzy_skin_painted(); }); } - static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART) { if (mesh.empty()) @@ -1734,8 +1734,14 @@ void ModelObject::ensure_on_bed(bool allow_negative_z) else z_offset = -this->min_z(); - if (z_offset != 0.0) - translate_instances(z_offset * Vec3d::UnitZ()); + if (z_offset != 0.0) { + for (size_t i = 0; i < instances.size(); ++i) { + if (!instances[i]->auto_drop) + continue; + + translate_instance(i, z_offset * Vec3d::UnitZ()); + } + } } void ModelObject::translate_instances(const Vec3d& vector) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 35333d6e2e..7d483e61c5 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -1251,6 +1251,7 @@ public: ModelInstanceEPrintVolumeState print_volume_state; // Whether or not this instance is printable bool printable; + bool auto_drop; bool use_loaded_id_for_label {false}; int arrange_order = 0; // BBS size_t loaded_id = 0; // BBS @@ -1379,7 +1380,11 @@ private: Polygon convex_hull; // BBS // Constructor, which assigns a new unique ID. - explicit ModelInstance(ModelObject* object) : print_volume_state(ModelInstancePVS_Inside), printable(true), object(object), m_assemble_initialized(false) { assert(this->id().valid()); } + explicit ModelInstance(ModelObject* object) + : print_volume_state(ModelInstancePVS_Inside), printable(true), auto_drop(true), object(object), m_assemble_initialized(false) + { + assert(this->id().valid()); + } // Constructor, which assigns a new unique ID. explicit ModelInstance(ModelObject *object, const ModelInstance &other) : m_transformation(other.m_transformation) @@ -1387,6 +1392,7 @@ private: , m_offset_to_assembly(other.m_offset_to_assembly) , print_volume_state(ModelInstancePVS_Inside) , printable(other.printable) + , auto_drop(other.auto_drop) , object(object) , m_assemble_initialized(false) { assert(this->id().valid() && this->id() != other.id()); } @@ -1400,7 +1406,7 @@ private: ModelInstance() : ObjectBase(-1), object(nullptr) { assert(this->id().invalid()); } // BBS. Add added members to archive. template void serialize(Archive& ar) { - ar(m_transformation, print_volume_state, printable, m_assemble_transformation, m_offset_to_assembly, m_assemble_initialized); + ar(m_transformation, print_volume_state, printable, auto_drop, m_assemble_transformation, m_offset_to_assembly, m_assemble_initialized); } }; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 14e0e7c119..bdbc73430b 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2358,6 +2358,13 @@ void GLCanvas3D::ensure_on_bed(unsigned int object_idx, bool allow_negative_z) InstancesToZMap instances_min_z; for (GLVolume* volume : m_volumes.volumes) { + ModelObject* mo = m_model->objects[volume->object_idx()]; + ModelInstance* mi = mo->instances[volume->instance_idx()]; + + if (!mi->auto_drop) { + continue; + } + if (volume->object_idx() == (int)object_idx && !volume->is_modifier) { double min_z = volume->transformed_convex_hull_bounding_box().min.z(); std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); @@ -4867,28 +4874,30 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) // Move instances/volumes ModelObject* model_object = m_model->objects[object_idx]; - if (model_object != nullptr) { - if (selection_mode == Selection::Instance) { - if (m_canvas_type == GLCanvas3D::ECanvasType::CanvasAssembleView) { - if ((model_object->instances[instance_idx]->get_assemble_offset() - v->get_instance_offset()).norm() > 1e-2) { - model_object->instances[instance_idx]->set_assemble_transformation(v->get_instance_transformation()); - } - } else { - model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); - } - } - else if (selection_mode == Selection::Volume) { - auto cur_mv = model_object->volumes[volume_idx]; - if (cur_mv->get_transformation() != v->get_volume_transformation()) { - cur_mv->set_transformation(v->get_volume_transformation()); - // BBS: backup - Slic3r::save_object_mesh(*model_object); - } - } + if (model_object == nullptr) + continue; - object_moved = true; - model_object->invalidate_bounding_box(); + if (selection_mode == Selection::Instance) { + if (m_canvas_type == GLCanvas3D::ECanvasType::CanvasAssembleView) { + if ((model_object->instances[instance_idx]->get_assemble_offset() - v->get_instance_offset()).norm() > 1e-2) { + model_object->instances[instance_idx]->set_assemble_transformation(v->get_instance_transformation()); + } + } else { + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); + } } + else if (selection_mode == Selection::Volume) { + auto cur_mv = model_object->volumes[volume_idx]; + if (cur_mv->get_transformation() != v->get_volume_transformation()) { + cur_mv->set_transformation(v->get_volume_transformation()); + // BBS: backup + Slic3r::save_object_mesh(*model_object); + } + } + + object_moved = true; + model_object->invalidate_bounding_box(); + } else if (object_idx >= 1000 && object_idx < 1000 + n_plates) { // Move a wipe tower proxy. @@ -4899,20 +4908,27 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) //BBS: notify instance updates to part plater list m_selection.notify_instance_update(-1, 0); - // Fixes flying instances + // Fixes sinking/flying instances (snaps object to buildplate) for (const std::pair& i : done) { - ModelObject* m = m_model->objects[i.first]; - const double shift_z = m->get_instance_min_z(i.second); + ModelObject* mo = m_model->objects[i.first]; + ModelInstance* mi = mo->instances[i.second]; + + if (!mi->auto_drop) { + continue; + } + + const double shift_z = mo->get_instance_min_z(i.second); //BBS: don't call translate if the z is zero if ((current_printer_technology() == ptSLA || shift_z > SINKING_Z_THRESHOLD) && (shift_z != 0.0f)) { const Vec3d shift(0.0, 0.0, -shift_z); m_selection.translate(i.first, i.second, shift); - m->translate_instance(i.second, shift); + mo->translate_instance(i.second, shift); //BBS: notify instance updates to part plater list m_selection.notify_instance_update(i.first, i.second); } wxGetApp().obj_list()->update_info_items(static_cast(i.first)); } + //BBS: nofity object list to update wxGetApp().plater()->sidebar().obj_list()->update_plate_values_for_items(); @@ -4986,40 +5002,47 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) // Rotate instances/volumes. ModelObject* model_object = m_model->objects[object_idx]; - if (model_object != nullptr) { - if (selection_mode == Selection::Instance) { - if (m_canvas_type == GLCanvas3D::ECanvasType::CanvasAssembleView) { - model_object->instances[instance_idx]->set_assemble_from_transform(v->get_instance_transformation().get_matrix()); - } else { - model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); - } + if (model_object == nullptr) + continue; + + if (selection_mode == Selection::Instance) { + if (m_canvas_type == GLCanvas3D::ECanvasType::CanvasAssembleView) { + model_object->instances[instance_idx]->set_assemble_from_transform(v->get_instance_transformation().get_matrix()); + } else { + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); } - else if (selection_mode == Selection::Volume) { - auto cur_mv = model_object->volumes[volume_idx]; - if (cur_mv->get_transformation() != v->get_volume_transformation()) { - cur_mv->set_transformation(v->get_volume_transformation()); - // BBS: backup - Slic3r::save_object_mesh(*model_object); - } - } - model_object->invalidate_bounding_box(); } + else if (selection_mode == Selection::Volume) { + auto cur_mv = model_object->volumes[volume_idx]; + if (cur_mv->get_transformation() != v->get_volume_transformation()) { + cur_mv->set_transformation(v->get_volume_transformation()); + // BBS: backup + Slic3r::save_object_mesh(*model_object); + } + } + model_object->invalidate_bounding_box(); } //BBS: notify instance updates to part plater list m_selection.notify_instance_update(-1, -1); + if (m_canvas_type != CanvasAssembleView) { - // Fixes sinking/flying instances + // Fixes sinking/flying instances (snaps object to buildplate) for (const std::pair &i : done) { - ModelObject *m = m_model->objects[i.first]; + ModelObject *mo = m_model->objects[i.first]; + ModelInstance* mi = mo->instances[i.second]; + + if (!mi->auto_drop) { + continue; + } // BBS: don't call translate if the z is zero - const double shift_z = m->get_instance_min_z(i.second); + const double shift_z = mo->get_instance_min_z(i.second); // leave sinking instances as sinking if ((min_zs.find({i.first, i.second})->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) && (shift_z != 0.0f)) { const Vec3d shift(0.0, 0.0, -shift_z); m_selection.translate(i.first, i.second, shift); - m->translate_instance(i.second, shift); + mo->translate_instance(i.second, shift); // BBS: notify instance updates to part plater list m_selection.notify_instance_update(i.first, i.second); } @@ -5027,6 +5050,7 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) wxGetApp().obj_list()->update_info_items(static_cast(i.first)); } } + //BBS: nofity object list to update wxGetApp().plater()->sidebar().obj_list()->update_plate_values_for_items(); @@ -5074,42 +5098,49 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) // Rotate instances/volumes ModelObject* model_object = m_model->objects[object_idx]; - if (model_object != nullptr) { - if (selection_mode == Selection::Instance) { - model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); - } - else if (selection_mode == Selection::Volume) { - auto cur_mv = model_object->volumes[volume_idx]; - if (cur_mv->get_transformation() != v->get_volume_transformation()) { - model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); - cur_mv->set_transformation(v->get_volume_transformation()); - // BBS: backup - Slic3r::save_object_mesh(*model_object); - } - } - model_object->invalidate_bounding_box(); + if (model_object == nullptr) + continue; + + if (selection_mode == Selection::Instance) { + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); } + else if (selection_mode == Selection::Volume) { + auto cur_mv = model_object->volumes[volume_idx]; + if (cur_mv->get_transformation() != v->get_volume_transformation()) { + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); + cur_mv->set_transformation(v->get_volume_transformation()); + // BBS: backup + Slic3r::save_object_mesh(*model_object); + } + } + model_object->invalidate_bounding_box(); } //BBS: notify instance updates to part plater list m_selection.notify_instance_update(-1, -1); - // Fixes sinking/flying instances + // Fixes sinking/flying instances (snaps object to buildplate) for (const std::pair& i : done) { - ModelObject* m = m_model->objects[i.first]; + ModelObject* mo = m_model->objects[i.first]; + ModelInstance* mi = mo->instances[i.second]; + + if (!mi->auto_drop) { + continue; + } //BBS: don't call translate if the z is zero - double shift_z = m->get_instance_min_z(i.second); + double shift_z = mo->get_instance_min_z(i.second); // leave sinking instances as sinking if ((min_zs.empty() || min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) && (shift_z != 0.0f)) { Vec3d shift(0.0, 0.0, -shift_z); m_selection.translate(i.first, i.second, shift); - m->translate_instance(i.second, shift); + mo->translate_instance(i.second, shift); //BBS: notify instance updates to part plater list m_selection.notify_instance_update(i.first, i.second); } wxGetApp().obj_list()->update_info_items(static_cast(i.first)); } + //BBS: nofity object list to update wxGetApp().plater()->sidebar().obj_list()->update_plate_values_for_items(); //BBS: notify object info update @@ -5179,35 +5210,41 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type) // Mirror instances/volumes ModelObject* model_object = m_model->objects[object_idx]; - if (model_object != nullptr) { - if (selection_mode == Selection::Instance) - model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); - else if (selection_mode == Selection::Volume) { - if (model_object->volumes[volume_idx]->get_transformation() != v->get_volume_transformation()) { - model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); - // BBS: backup - Slic3r::save_object_mesh(*model_object); - } - } + if (model_object == nullptr) + continue; - model_object->invalidate_bounding_box(); + if (selection_mode == Selection::Instance) + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); + else if (selection_mode == Selection::Volume) { + if (model_object->volumes[volume_idx]->get_transformation() != v->get_volume_transformation()) { + model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); + // BBS: backup + Slic3r::save_object_mesh(*model_object); + } } + + model_object->invalidate_bounding_box(); } //BBS: notify instance updates to part plater list m_selection.notify_instance_update(-1, -1); - // Fixes sinking/flying instances + // Fixes sinking/flying instances (snaps object to buildplate) for (const std::pair& i : done) { - ModelObject* m = m_model->objects[i.first]; + ModelObject* mo = m_model->objects[i.first]; + ModelInstance* mi = mo->instances[i.second]; + + if (!mi->auto_drop) { + continue; + } //BBS: don't call translate if the z is zero - double shift_z = m->get_instance_min_z(i.second); + double shift_z = mo->get_instance_min_z(i.second); // leave sinking instances as sinking if ((min_zs.empty() || min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD)&&(shift_z != 0.0f)) { Vec3d shift(0.0, 0.0, -shift_z); m_selection.translate(i.first, i.second, shift); - m->translate_instance(i.second, shift); + mo->translate_instance(i.second, shift); //BBS: notify instance updates to part plater list m_selection.notify_instance_update(i.first, i.second); } @@ -5216,10 +5253,8 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type) //BBS: notify instance updates to part plater list PartPlateList &plate_list = wxGetApp().plater()->get_partplate_list(); plate_list.notify_instance_update(i.first, i.second); - - //BBS: nofity object list to update - wxGetApp().plater()->sidebar().obj_list()->update_plate_values_for_items(); - } + } + //BBS: nofity object list to update wxGetApp().plater()->sidebar().obj_list()->update_plate_values_for_items(); diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 5c3c0267ee..831b4c5f34 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -855,6 +855,23 @@ wxMenuItem* MenuFactory::append_menu_item_printable(wxMenu* menu) return menu_item_printable; } +wxMenuItem* MenuFactory::append_menu_item_auto_drop(wxMenu* menu) +{ + wxString menu_text = _L("Auto Drop"); + wxString menu_tooltip = _L("Automatically drops the selected object to the build plate"); + wxMenuItem* menu_item_auto_drop = append_menu_check_item( + menu, wxID_ANY, menu_text, menu_tooltip, + [](wxCommandEvent&) { obj_list()->toggle_auto_drop(); }, menu); + + m_parent->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt) { + bool check = wxGetApp().plater()->get_selection().get_auto_drop(); + evt.Check(check); + }, + menu_item_auto_drop->GetId()); + + return menu_item_auto_drop; +} + void MenuFactory::append_menu_item_rename(wxMenu* menu) { append_menu_item(menu, wxID_ANY, _L("Rename"), "", @@ -1416,6 +1433,10 @@ void MenuFactory::create_extra_object_menu() // Set filament insert menu item here // Set Printable wxMenuItem* menu_item_printable = append_menu_item_printable(&m_object_menu); + m_object_menu.AppendSeparator(); + wxMenuItem* menu_item_auto_drop = append_menu_item_auto_drop(&m_object_menu); + m_object_menu.AppendSeparator(); + append_menu_item_per_object_process(&m_object_menu); // Enter per object parameters append_menu_item_per_object_settings(&m_object_menu); @@ -1845,8 +1866,14 @@ wxMenu* MenuFactory::multi_selection_menu() menu->AppendSeparator(); append_menu_item_set_printable(menu); + menu->AppendSeparator(); + + append_menu_item_set_auto_drop(menu); + menu->AppendSeparator(); + append_menu_item_per_object_process(menu); menu->AppendSeparator(); + append_menu_items_convert_unit(menu); append_menu_item_replace_all_with_stl(menu); //BBS @@ -2032,7 +2059,7 @@ void MenuFactory::append_menu_item_drop(wxMenu* menu) if (plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasView3D) return false; else { - return (plater()->get_view3D_canvas3D()->get_selection().get_bounding_box().min.z() != 0); + return (plater()->get_view3D_canvas3D()->get_selection().get_bounding_box().min.z() > SINKING_Z_THRESHOLD); } //disable if model is on the bed / not in View3D }, m_parent); } @@ -2184,7 +2211,7 @@ void MenuFactory::append_menu_item_change_filament(wxMenu* menu) void MenuFactory::append_menu_item_set_printable(wxMenu* menu) { const Selection& selection = plater()->canvas3D()->get_selection(); - bool all_printable = true; + bool all_printable = true; ObjectList* list = obj_list(); wxDataViewItemArray sels; list->GetSelections(sels); @@ -2212,6 +2239,29 @@ void MenuFactory::append_menu_item_set_printable(wxMenu* menu) }, menu_item_set_printable->GetId()); } +void MenuFactory::append_menu_item_set_auto_drop(wxMenu* menu) +{ + const Selection& selection = plater()->canvas3D()->get_selection(); + const bool current_auto_drop = selection.get_auto_drop(); + + wxString menu_text = _L("Auto Drop"); + wxString menu_tooltip = _L("Automatically snaps the selected object to the build plate"); + wxMenuItem* menu_item_set_auto_drop = append_menu_check_item( + menu, wxID_ANY, menu_text, menu_tooltip, + [this, current_auto_drop](wxCommandEvent&) { + Selection& selection = plater()->canvas3D()->get_selection(); + selection.set_auto_drop(!current_auto_drop); + }, + menu); + m_parent->Bind( + wxEVT_UPDATE_UI, + [current_auto_drop](wxUpdateUIEvent& evt) { + evt.Check(current_auto_drop); + plater()->set_current_canvas_as_dirty(); + }, + menu_item_set_auto_drop->GetId()); +} + void MenuFactory::append_menu_item_locked(wxMenu* menu) { const std::vector names = { _L("Unlock"), _L("Lock") }; diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index 0ae0e96a73..b3915c0705 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -137,6 +137,7 @@ private: wxMenuItem* append_menu_item_change_type(wxMenu* menu); wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu); wxMenuItem* append_menu_item_printable(wxMenu* menu); + wxMenuItem* append_menu_item_auto_drop(wxMenu* menu); void append_menu_item_rename(wxMenu* menu); wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu); //wxMenuItem* append_menu_item_simplify(wxMenu* menu); @@ -171,6 +172,7 @@ private: void append_menu_item_per_object_settings(wxMenu* menu); void append_menu_item_change_filament(wxMenu* menu); void append_menu_item_set_printable(wxMenu* menu); + void append_menu_item_set_auto_drop(wxMenu* menu); void append_menu_item_locked(wxMenu* menu); void append_menu_item_fill_bed(wxMenu *menu); void append_menu_item_plate_name(wxMenu *menu); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 5a3013909a..c0a790961b 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -914,15 +914,15 @@ void ObjectList::object_config_options_changed(const ObjectVolumeID& ov_id) } } -void ObjectList::printable_state_changed(const std::vector& ov_ids) +void ObjectList::printable_state_changed(const std::vector model_objects) { std::vector obj_idxs; - for (const ObjectVolumeID ov_id : ov_ids) { - if (ov_id.object == nullptr) + for (const ModelObject* mo : model_objects) { + if (mo == nullptr) continue; - ModelInstance* mi = ov_id.object->instances[0]; - wxDataViewItem obj_item = m_objects_model->GetObjectItem(ov_id.object); + ModelInstance* mi = mo->instances[0]; + wxDataViewItem obj_item = m_objects_model->GetObjectItem(mo); m_objects_model->SetObjectPrintableState(mi->printable ? piPrintable : piUnprintable, obj_item); int obj_idx = m_objects_model->GetObjectIdByItem(obj_item); @@ -939,6 +939,19 @@ void ObjectList::printable_state_changed(const std::vector& ov_i wxGetApp().plater()->update(); } +void ObjectList::printable_state_changed(const std::vector& ov_ids) +{ + std::vector model_objects; + model_objects.reserve(ov_ids.size()); + + for (const ObjectVolumeID& ov_id : ov_ids) { + if (ov_id.object != nullptr) + model_objects.emplace_back(ov_id.object); + } + + printable_state_changed(model_objects); +} + void ObjectList::assembly_plate_object_name() { m_objects_model->assembly_name(); @@ -1748,6 +1761,8 @@ void ObjectList::key_event(wxKeyEvent& event) decrease_instances(); else if (event.GetUnicodeKey() == 'p') toggle_printable_state(); + else if (event.GetUnicodeKey() == 'd') + toggle_auto_drop(); else if (filaments_count() > 1) { std::vector numbers = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; wxChar key_char = event.GetUnicodeKey(); @@ -3044,6 +3059,17 @@ void ObjectList::merge(bool to_multipart_object) volume->config.set_key_value("extruder", option->clone()); } + // merge printable and auto_drop values + // non-default have priority -> if one object has printable == false, + // then merged object will also have printable == false + if (object->instances[0]->printable == false) { + new_object->printable = false; + new_object->instances[0]->printable = false; + } + if (object->instances[0]->auto_drop == false) { + new_object->instances[0]->auto_drop = false; + } + // merge layers for (const auto& range : object->layer_config_ranges) new_object->layer_config_ranges.emplace(range); @@ -3077,6 +3103,9 @@ void ObjectList::merge(bool to_multipart_object) // Add new object(merged) to the object_list add_object_to_list(m_objects->size() - 1); + if (new_object->printable == false) { + wxGetApp().obj_list()->printable_state_changed({new_object}); + } select_item(m_objects_model->GetItemById(m_objects->size() - 1)); update_selections_on_canvas(); } @@ -6600,6 +6629,52 @@ void ObjectList::toggle_printable_state() wxGetApp().plater()->reload_paint_after_background_process_apply(); } +void ObjectList::toggle_auto_drop() +{ + wxDataViewItemArray sels; + GetSelections(sels); + if (sels.IsEmpty()) + return; + + for (auto item : sels) { + ItemType type = m_objects_model->GetItemType(item); + if (!(type & (itObject | itInstance))) + return; + } + + const bool current_auto_drop = wxGetApp().plater()->get_selection().get_auto_drop(); + + take_snapshot(""); + + std::vector obj_idxs; + for (auto item : sels) { + int obj_idx = m_objects_model->GetObjectIdByItem(item); + ModelObject* obj = object(obj_idx); + + obj_idxs.emplace_back(static_cast(obj_idx)); + + ItemType type = m_objects_model->GetItemType(item); + // set auto_drop value for selected instance/instances in object + if (type == itInstance) { + int inst_idx = m_objects_model->GetInstanceIdByItem(item); + obj->instances[inst_idx]->auto_drop = !current_auto_drop; + } else { + for (auto inst : obj->instances) + inst->auto_drop = !current_auto_drop; + + if (current_auto_drop == false) + obj->ensure_on_bed(); + } + } + + sort(obj_idxs.begin(), obj_idxs.end()); + obj_idxs.erase(unique(obj_idxs.begin(), obj_idxs.end()), obj_idxs.end()); + + // update scene + wxGetApp().plater()->update(); + wxGetApp().plater()->reload_paint_after_background_process_apply(); +} + void ObjectList::enable_layers_editing() { wxDataViewItemArray sels; diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index fef8230a28..8fe85dc7f6 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -449,6 +449,7 @@ public: //update printable state for item from objects model void update_printable_state(int obj_idx, int instance_idx); void toggle_printable_state(); + void toggle_auto_drop(); void enable_layers_editing(); //BBS: remove const qualifier @@ -463,6 +464,7 @@ public: void on_plate_selected(int plate_index); void notify_instance_updated(int obj_idx); void object_config_options_changed(const ObjectVolumeID& ov_id); + void printable_state_changed(const std::vector model_objects); void printable_state_changed(const std::vector& ov_ids); // search objectlist diff --git a/src/slic3r/GUI/GUI_ObjectTable.cpp b/src/slic3r/GUI/GUI_ObjectTable.cpp index 103b15b1f3..4fc40db74b 100644 --- a/src/slic3r/GUI/GUI_ObjectTable.cpp +++ b/src/slic3r/GUI/GUI_ObjectTable.cpp @@ -1520,12 +1520,7 @@ void ObjectGridTable::update_value_to_object(Model* model, ObjectGridRow* grid_r object->printable = grid_row->printable.value; object->instances[0]->printable = object->printable; - std::vector object_volume_ids; - ObjectVolumeID object_volume_id; - object_volume_id.object = object; - object_volume_id.volume = nullptr; - object_volume_ids.push_back(object_volume_id); - wxGetApp().obj_list()->printable_state_changed(object_volume_ids); + wxGetApp().obj_list()->printable_state_changed({object}); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", change object %1%'s printable to %2%")%object->module_name %object->printable; } } diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index a7d2f711bf..ed4ad60f55 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -205,8 +205,8 @@ void ObjectDataViewModelNode::set_printable_icon(PrintIndicator printable) if (m_printable == printable) return; m_printable = printable; - m_printable_icon = m_printable == piUndef ? m_empty_bmp : - create_scaled_bitmap(m_printable == piPrintable ? "check_on" : "check_off_focused"); + m_printable_icon = m_printable == piUndef ? + m_empty_bmp : create_scaled_bitmap(m_printable == piPrintable ? "check_on" : "check_off_focused"); } void ObjectDataViewModelNode::set_variable_height_icon(VaryHeightIndicator vari_height) { diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index a980e204ad..137863a36c 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -241,7 +241,7 @@ public: void SetExtruder(const wxString &extruder) { m_extruder = extruder; } void SetWarningIconName(const std::string& warning_icon_name) { m_warning_icon_name = warning_icon_name; } void SetLock(bool has_lock) { m_has_lock = has_lock; } - const wxBitmap& GetBitmap() const { return m_bmp; } + const wxBitmap& GetBitmap() const { return m_bmp; } const wxString& GetName() const { return m_name; } ItemType GetType() const { return m_type; } InfoItemType GetInfoItemType() const { return m_info_item_type; } @@ -249,16 +249,16 @@ public: int GetIdx() const { return m_idx; } //BBS: add part plate related logic void SetPlateIdx(const int& idx); - int GetPlateIdx() const { return m_plate_idx; } + int GetPlateIdx() const { return m_plate_idx; } ModelVolumeType GetVolumeType() { return m_volume_type; } t_layer_height_range GetLayerRange() const { return m_layer_range; } wxString GetExtruder() { return m_extruder; } PrintIndicator IsPrintable() const { return m_printable; } - VaryHeightIndicator IsVaribaleHeight() const { return m_variable_height; } + VaryHeightIndicator IsVaribaleHeight() const { return m_variable_height; } // BBS bool HasColorPainting() const { return m_color_enable; } - bool HasSupportPainting() const { return m_support_enable; } - bool HasSinking() const { return m_sink_enable; } + bool HasSupportPainting() const { return m_support_enable; } + bool HasSinking() const { return m_sink_enable; } bool IsActionEnabled() const { return m_action_enable; } void UpdateExtruderAndColorIcon(wxString extruder = ""); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 578b20ed87..7a151658a0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4507,7 +4507,7 @@ struct Plater::priv // BBS: backup & restore std::vector load_files(const std::vector& input_files, LoadStrategy strategy, bool ask_multi = false); - std::vector load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false, bool split_object = false); + std::vector load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false, bool split_object = false, bool auto_drop = true); fs::path get_export_file_path(GUI::FileType file_type); wxString get_export_file(GUI::FileType file_type); @@ -4538,7 +4538,8 @@ struct Plater::priv void center_selection(); void drop_selection(); void mirror(Axis axis); - void split_object(); + void split_object(bool auto_drop = true); + void split_object(int obj_idx, bool auto_drop = true); void split_volume(); void scale_selection_to_fit_print_volume(); @@ -6715,14 +6716,37 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (new_model != nullptr && new_model->objects.size() > 1) { //BBS do not popup this dialog + bool new_model_auto_drop = true; + int single_object_answer = false; if (ask_multi) { - MessageDialog msg_dlg(q, _L("Load these files as a single object with multiple parts?\n"), _L("Object with multiple parts was detected"), - wxICON_WARNING | wxYES | wxNO); - if (msg_dlg.ShowModal() == wxID_YES) { new_model->convert_multipart_object(filaments_cnt); } + RichMessageDialog dlg(q, _L("Load these files as a single object with multiple parts?\n"), + _L("Object with multiple parts was detected"), wxICON_QUESTION | wxYES_NO); + + dlg.ShowCheckBox(_L("Auto-Drop"), true); + single_object_answer = dlg.ShowModal(); + + if (dlg.IsCheckBoxChecked() == false) + new_model_auto_drop = false; + + // convert to multipart and split after load_model_objects + // to keep relative positioning if auto_drop == false + if (single_object_answer == wxID_YES || new_model_auto_drop == false) + new_model->convert_multipart_object(filaments_cnt); } - auto loaded_idxs = load_model_objects(new_model->objects); + // TODO + // DONE always convert to multipart, split afterwards to retain relative position + // DONE if !auto_drop move all objects over the z-position 0, so that none are clipped by the bed. + // DONE retain auto_drop (and printable) state when assembling or splitting objects. + // DONE when manually split to object ask users if looks_like_multipart and none have auto_drob disabled if they want to disable auto_drop for all resulting objects. + // - add icon in object list, similar to fuzzy painting, etc. + + auto loaded_idxs = load_model_objects(new_model->objects, false, false, new_model_auto_drop); obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); + + if (single_object_answer == wxID_NO && new_model_auto_drop == false) { + split_object(loaded_idxs[0], new_model_auto_drop); + } } if (load_config) { @@ -6889,7 +6913,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ #define AUTOPLACEMENT_ON_LOAD -std::vector Plater::priv::load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z, bool split_object) +std::vector Plater::priv::load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z, bool split_object, bool auto_drop) { const Vec3d bed_size = Slic3r::to_3d(this->bed.build_volume().bounding_volume2d().size(), 1.0) - 2.0 * Vec3d::Ones(); @@ -6916,7 +6940,7 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs& mode #else /* AUTOPLACEMENT_ON_LOAD */ // if object has no defined position(s) we need to rearrange everything after loading // need_arrange = true; - // add a default instance and center object around origin + // add a default instance and center object around origin object->center_around_origin(); // also aligns object to Z = 0 ModelInstance* instance = object->add_instance(); @@ -6925,7 +6949,7 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs& mode instance->set_offset(Slic3r::to_3d(this->bed.build_volume().bed_center(), -object->origin_translation(2))); #endif /* AUTOPLACEMENT_ON_LOAD */ } - + //BBS: when the object is too large, let the user choose whether to scale it down for (size_t i = 0; i < object->instances.size(); ++i) { ModelInstance* instance = object->instances[i]; @@ -6955,7 +6979,20 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs& mode } } - object->ensure_on_bed(allow_negative_z); + if (!auto_drop) { + for (size_t i = 0; i < object->instances.size(); ++i) { + ModelInstance* instance = object->instances[i]; + instance->auto_drop = auto_drop; + } + + // if under the bed, move over the bed + double dist_to_bed = std::min(object->min_z(), double(0)); + object->translate_instances(Vec3d(0, 0, -dist_to_bed)); + } + else { + object->ensure_on_bed(allow_negative_z); + } + if (!split_object) { //BBS initial assemble transformation for (ModelObject* model_object : model.objects) { @@ -6965,7 +7002,7 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs& mode model_object->instances[i]->set_assemble_transformation(model_object->instances[i]->get_transformation()); } } - } + } } } @@ -7032,7 +7069,7 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs& mode // which is updated after a view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH) call for (const size_t idx : obj_idxs) wxGetApp().obj_list()->update_info_items(idx); - + object_list_changed(); this->schedule_background_process(); @@ -7484,9 +7521,16 @@ void Plater::find_new_position(const ModelInstancePtrs &instances) m.apply(); } -void Plater::priv::split_object() -{ +// split selected object into multiple objects by its volumes +void Plater::priv::split_object(bool auto_drop /* = true */) +{ int obj_idx = get_selected_object_idx(); + priv::split_object(obj_idx, auto_drop); +} + +// split provided object into multiple objects by its volumes +void Plater::priv::split_object(int obj_idx, bool auto_drop /* = true */) +{ if (obj_idx == -1) return; @@ -7512,12 +7556,30 @@ void Plater::priv::split_object() Plater::TakeSnapshot snapshot(q, "Split to Objects"); + auto is_atleast_one_floating = [new_objects]() { + for (ModelObject* new_object : new_objects) { + if (new_object->get_instance_min_z(0) >= SINKING_MIN_Z_THRESHOLD) + return true; + } + return false; + }; + bool split_auto_drop = auto_drop; + if (current_model_object->instances[0]->auto_drop && is_atleast_one_floating()) { + MessageDialog dlg(q, _L("Disable Auto-Drop to preserve z positioning?\n"), + _L("Object with floating parts was detected"), wxICON_QUESTION | wxYES_NO); + + if (dlg.ShowModal() == wxID_YES) + split_auto_drop = false; + } + remove(obj_idx); // load all model objects at once, otherwise the plate would be rearranged after each one // causing original positions not to be kept //BBS: set split_object to true to avoid re-compute assemble matrix - std::vector idxs = load_model_objects(new_objects, false, true); + std::vector idxs = load_model_objects(new_objects, false, true, split_auto_drop); + + wxGetApp().plater()->get_view3D_canvas3D()->update_instance_printable_state_for_objects(idxs); // select newly added objects for (size_t idx : idxs) @@ -16841,11 +16903,11 @@ void Plater::suppress_background_process(const bool stop_background_process) this->p->suppressed_backround_processing_update = true; } -void Plater::center_selection() { p->center_selection(); } -void Plater::drop_selection() { p->drop_selection(); } -void Plater::mirror(Axis axis) { p->mirror(axis); } -void Plater::split_object() { p->split_object(); } -void Plater::split_volume() { p->split_volume(); } +void Plater::center_selection() { p->center_selection(); } +void Plater::drop_selection() { p->drop_selection(); } +void Plater::mirror(Axis axis) { p->mirror(axis); } +void Plater::split_object(bool auto_drop) { p->split_object(auto_drop); } +void Plater::split_volume() { p->split_volume(); } void Plater::optimize_rotation() { auto &w = get_ui_job_worker(); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 3708e52b42..83c5dd6b75 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -643,7 +643,8 @@ public: void drop_selection(); void search(bool plater_is_active, Preset::Type type, wxWindow *tag, TextInput *etag, wxWindow *stag); void mirror(Axis axis); - void split_object(); + void split_object(bool auto_drop = true); + void split_object(int obj_idx, bool auto_drop = true); void split_volume(); void optimize_rotation(); // find all empty cells on the plate and won't overlap with exclusion areas diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 544539f788..265c43b422 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -525,8 +525,32 @@ void Selection::center() void Selection::drop() { + if (this->get_bounding_box().min.z() < SINKING_Z_THRESHOLD) { + return; // shouldnt happen, but better check anyways, already checked in append_menu_item_drop() + } + + wxGetApp().plater()->take_snapshot(L("Move Object")); + this->move_to_center(Vec3d(0, 0, -this->get_bounding_box().min.z())); - wxGetApp().plater()->get_view3D_canvas3D()->do_move(L("Move Object")); + + for (unsigned int i : m_list) { + GLVolume& volume = *(*m_volumes)[i]; + ModelObject* model_object = m_model->objects[volume.object_idx()]; + + if (model_object != nullptr) { + if (m_mode == Volume) { + ModelVolume* cur_mv = model_object->volumes[volume.volume_idx()]; + cur_mv->set_transformation(volume.get_volume_transformation()); + } else if (m_mode == Instance) { + model_object->instances[volume.instance_idx()]->set_transformation(volume.get_instance_transformation()); + } + + model_object->invalidate_bounding_box(); + this->notify_instance_update(volume.object_idx(), volume.instance_idx()); + } + } + + wxGetApp().plater()->get_view3D_canvas3D()->post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_MOVED)); } void Selection::center_plate(const int plate_idx) { @@ -550,11 +574,9 @@ void Selection::set_printable(bool printable) return; std::set> instances_idxs; - for (ObjectIdxsToInstanceIdxsMap::iterator obj_it = m_cache.content.begin(); obj_it != m_cache.content.end(); ++obj_it) - { - for (InstanceIdxsList::reverse_iterator inst_it = obj_it->second.rbegin(); inst_it != obj_it->second.rend(); ++inst_it) - { - instances_idxs.insert(std::make_pair(obj_it->first, *inst_it)); + for (const auto& [obj_idx, inst_list] : m_cache.content) { + for (int inst_idx : inst_list) { + instances_idxs.insert({obj_idx, inst_idx}); } } @@ -562,15 +584,66 @@ void Selection::set_printable(bool printable) wxGetApp().plater()->take_snapshot(snapshot_text); // set printable value for all instances in object - for (const std::pair& i : instances_idxs) + for (const auto& [obj_idx, inst_idx] : instances_idxs) { - ModelObject* object = m_model->objects[i.first]; + ModelObject* object = m_model->objects[obj_idx]; for (auto inst : object->instances) inst->printable = printable; - wxGetApp().obj_list()->update_printable_state(i.first, i.second); + wxGetApp().obj_list()->update_printable_state(obj_idx, inst_idx); //update printable state on canvas - wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object((size_t)i.first); + wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object((size_t) obj_idx); + } + + // update scene + wxGetApp().plater()->update(); +} + +bool Selection::get_auto_drop() const { + if (!m_valid) + return true; + + std::set> instances_idxs; + for (const auto& [obj_idx, inst_list] : m_cache.content) { + for (int inst_idx : inst_list) { + instances_idxs.insert({obj_idx, inst_idx}); + } + } + + // return false, if one of the instances in the selection has auto_drop disabled, otherwise return true + for (const auto& [obj_idx, inst_idx] : instances_idxs) { + ModelObject* object = m_model->objects[obj_idx]; + for (const auto* inst : object->instances) { + if (!inst->auto_drop) { + return false; + } + } + } + + return true; +} + +void Selection::set_auto_drop(bool enabled) +{ + if (!m_valid) + return; + + std::set> instances_idxs; + for (const auto& [obj_idx, inst_list] : m_cache.content) { + for (int inst_idx : inst_list) { + instances_idxs.insert({obj_idx, inst_idx}); + } + } + + std::string snapshot_text = (boost::format("%1%") % (enabled ? "Set Selection Auto-Drop Enabled" : "Set Selection Auto-Drop Disabled")).str(); + wxGetApp().plater()->take_snapshot(snapshot_text); + + // set auto_drop value for all instances in object + for (const auto& [obj_idx, inst_idx] : instances_idxs) { + ModelObject* object = m_model->objects[obj_idx]; + for (auto* inst : object->instances) { + inst->auto_drop = enabled; + } } // update scene @@ -1601,7 +1674,6 @@ void Selection::scale_and_translate(const Vec3d &scale, const Vec3d &world_trans synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH - ensure_on_bed(); set_bounding_boxes_dirty(); if (wxGetApp().plater()->canvas3D()->get_canvas_type() != GLCanvas3D::ECanvasType::CanvasAssembleView) { wxGetApp().plater()->canvas3D()->requires_check_outside_state(); @@ -1612,7 +1684,6 @@ void Selection::mirror(Axis axis, TransformationType transformation_type) { const Vec3d mirror((axis == X) ? -1.0 : 1.0, (axis == Y) ? -1.0 : 1.0, (axis == Z) ? -1.0 : 1.0); scale_and_translate(mirror, Vec3d::Zero(), transformation_type); - } void Selection::translate(unsigned int object_idx, const Vec3d& displacement) @@ -1927,7 +1998,8 @@ void Selection::render(float scale_factor) m_scale_factor = scale_factor; // render cumulative bounding box of selected volumes const auto& [box, trafo] = get_bounding_box_in_current_reference_system(); - render_bounding_box(box, trafo, + const bool auto_drop = get_auto_drop(); + render_bounding_box(box, trafo, auto_drop, wxGetApp().plater()->canvas3D()->get_canvas_type() == GLCanvas3D::ECanvasType::CanvasAssembleView ? ColorRGB::YELLOW(): ColorRGB::WHITE()); render_synchronized_volumes(); } @@ -2530,86 +2602,128 @@ void Selection::render_synchronized_volumes() box = v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix()); trafo = v.get_instance_transformation().get_matrix(); } - render_bounding_box(box, trafo, ColorRGB::YELLOW()); + const bool auto_drop = get_auto_drop(); + render_bounding_box(box, trafo, auto_drop, ColorRGB::YELLOW()); } } } -void Selection::render_bounding_box(const BoundingBoxf3& box, const Transform3d& trafo, const ColorRGB& color) +void Selection::render_bounding_box(const BoundingBoxf3& box, const Transform3d& trafo, const bool auto_drop, const ColorRGB& color) { const BoundingBoxf3& curr_box = m_box.get_bounding_box(); + const bool curr_auto_drop = m_auto_drop; - if (!m_box.is_initialized() || !is_approx(box.min, curr_box.min) || !is_approx(box.max, curr_box.max)) { + if (!m_box.is_initialized() || !is_approx(box.min, curr_box.min) || !is_approx(box.max, curr_box.max) || curr_auto_drop != auto_drop) { m_box.reset(); + m_auto_drop = auto_drop; const Vec3f b_min = box.min.cast(); const Vec3f b_max = box.max.cast(); const Vec3f size = 0.2f * box.size().cast(); + // 48 brackets + (24 if auto_drop == false) + int total_vertices = 48 + (auto_drop ? 0 : 24); + GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; - init_data.reserve_vertices(48); - init_data.reserve_indices(48); + init_data.reserve_vertices(total_vertices); + init_data.reserve_indices(total_vertices); // vertices - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y() + size.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z() + size.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y() + size.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z() + size.z())); + + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y() + size.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z() + size.z())); + + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y() - size.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z() + size.z())); + + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y() - size.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z() + size.z())); + + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y() + size.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z() - size.z())); + + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y() + size.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z() - size.z())); + + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y() - size.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z() - size.z())); + + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y() - size.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z() - size.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y() + size.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z() + size.z())); + // Indicate that auto_drop == false, by drawing an arrow on bottom corners + if (!auto_drop) { + float visual_scale = m_scale_factor; + float head_s = 2.0f * visual_scale; // Width of the arrowhead wings + float z_gap = 1.0f * visual_scale; // Gap between box and arrow tip + float arrow_h = 5.0f * visual_scale; // Total height of the arrow stem - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y() - size.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z() + size.z())); + // 4 bottom corners + std::vector corners = {{b_min.x(), b_min.y(), b_min.z()}, + {b_max.x(), b_min.y(), b_min.z()}, + {b_max.x(), b_max.y(), b_min.z()}, + {b_min.x(), b_max.y(), b_min.z()}}; - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y() - size.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z() + size.z())); + for (const auto& pt : corners) { + Vec3f tip = {pt.x(), pt.y(), pt.z() - z_gap}; + Vec3f base = {pt.x(), pt.y(), tip.z() - arrow_h}; - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y() + size.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z() - size.z())); + // Stem + init_data.add_vertex(base); + init_data.add_vertex(tip); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y() + size.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z() - size.z())); + // Wings + // Determine direction to the center of the footprint + float dirX = (pt.x() == b_min.x()) ? head_s : -head_s; + float dirY = (pt.y() == b_min.y()) ? head_s : -head_s; - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y() - size.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z() - size.z())); + // Wing 1 (X-Inward) + init_data.add_vertex(tip); + init_data.add_vertex(Vec3f(tip.x() + dirX, tip.y(), tip.z() - head_s)); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y() - size.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z() - size.z())); + // Wing 2 (Y-Inward) + init_data.add_vertex(tip); + init_data.add_vertex(Vec3f(tip.x(), tip.y() + dirY, tip.z() - head_s)); + } + } - // indices - for (unsigned int i = 0; i < 48; ++i) { + // Finalize indices + for (unsigned int i = 0; i < init_data.vertices_count(); ++i) { init_data.add_index(i); } @@ -2941,8 +3055,11 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ } else if (sync_rotation_type != SyncRotationType::NONE || mirrored) new_inst_trafo_j.linear() = (old_inst_trafo_j.linear() * old_inst_trafo_i.linear().inverse()) * curr_inst_trafo_i.linear(); - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) + + bool should_synchronize_z = m_model->objects[volume_j->object_idx()]->instances[volume_j->instance_idx()]->auto_drop == false; + if (should_synchronize_z && wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) new_inst_trafo_j.translation().z() = curr_inst_trafo_i.translation().z(); + assert(is_rotation_xy_synchronized(curr_inst_trafo_i, new_inst_trafo_j)); volume_j->set_instance_transformation(new_inst_trafo_j); done.insert(j); @@ -2985,17 +3102,25 @@ void Selection::ensure_on_bed() InstancesToZMap instances_min_z; for (size_t i = 0; i < m_volumes->size(); ++i) { - GLVolume* volume = (*m_volumes)[i]; - if (!volume->is_wipe_tower && !volume->is_modifier && - std::find(m_cache.sinking_volumes.begin(), m_cache.sinking_volumes.end(), i) == m_cache.sinking_volumes.end()) { - const double min_z = volume->transformed_convex_hull_bounding_box().min.z(); - std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); - InstancesToZMap::iterator it = instances_min_z.find(instance); - if (it == instances_min_z.end()) - it = instances_min_z.insert(InstancesToZMap::value_type(instance, DBL_MAX)).first; + GLVolume* volume = (*m_volumes)[i]; + ModelObject* mo = m_model->objects[volume->object_idx()]; + ModelInstance* mi = mo->instances[volume->instance_idx()]; - it->second = std::min(it->second, min_z); + if (mi->auto_drop == false + || volume->is_wipe_tower + || volume->is_modifier + || std::find(m_cache.sinking_volumes.begin(), m_cache.sinking_volumes.end(), i) != m_cache.sinking_volumes.end()) + { + continue; } + + const double min_z = volume->transformed_convex_hull_bounding_box().min.z(); + std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + InstancesToZMap::iterator it = instances_min_z.find(instance); + if (it == instances_min_z.end()) + it = instances_min_z.insert(InstancesToZMap::value_type(instance, DBL_MAX)).first; + + it->second = std::min(it->second, min_z); } for (GLVolume* volume : *m_volumes) { diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 0ad56930df..e381305072 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -181,6 +181,7 @@ private: GLModel m_arrow; GLModel m_curved_arrow; GLModel m_box; + bool m_auto_drop = true; struct Planes { std::array check_points{ Vec3f::Zero(), Vec3f::Zero() }; @@ -237,6 +238,8 @@ public: void drop(); void center_plate(const int plate_idx); void set_printable(bool printable); + bool get_auto_drop() const; + void set_auto_drop(bool enabled); void add_all(); void remove_all(); @@ -370,7 +373,7 @@ public: void render_bounding_box(const BoundingBoxf3& box, const ColorRGB& color, float scale) { m_scale_factor = scale; - render_bounding_box(box, Transform3d::Identity(), color); + render_bounding_box(box, Transform3d::Identity(), get_auto_drop(), color); } //BBS @@ -414,7 +417,7 @@ private: m_bounding_sphere.reset(); } void render_synchronized_volumes(); - void render_bounding_box(const BoundingBoxf3& box, const Transform3d& trafo, const ColorRGB& color); + void render_bounding_box(const BoundingBoxf3& box, const Transform3d& trafo, const bool auto_drop, const ColorRGB& color); void render_sidebar_position_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix); void render_sidebar_rotation_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix); //BBS: GUI refactor: add uniform_scale from gizmo