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 <hannowitzleb@gmail.com>
Co-authored-by: SoftFever <softfeverever@gmail.com>
Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
This commit is contained in:
Hanno Witzleb
2026-04-15 16:36:25 +02:00
committed by GitHub
parent f02aa7200e
commit f7ef8a14bd
15 changed files with 583 additions and 213 deletions

View File

@@ -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 << " </" << BUILD_TAG << ">\n";

View File

@@ -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)

View File

@@ -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<class Archive> 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);
}
};

View File

@@ -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<int, int> 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<int, int>& 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<size_t>(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<int, int> &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<size_t>(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<int, int>& 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<size_t>(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<int, int>& 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();

View File

@@ -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<wxString> names = { _L("Unlock"), _L("Lock") };

View File

@@ -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);

View File

@@ -914,15 +914,15 @@ void ObjectList::object_config_options_changed(const ObjectVolumeID& ov_id)
}
}
void ObjectList::printable_state_changed(const std::vector<ObjectVolumeID>& ov_ids)
void ObjectList::printable_state_changed(const std::vector<ModelObject*> model_objects)
{
std::vector<size_t> 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<ObjectVolumeID>& ov_i
wxGetApp().plater()->update();
}
void ObjectList::printable_state_changed(const std::vector<ObjectVolumeID>& ov_ids)
{
std::vector<ModelObject*> 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<wxChar> 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<size_t> 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<size_t>(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;

View File

@@ -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<ModelObject*> model_objects);
void printable_state_changed(const std::vector<ObjectVolumeID>& ov_ids);
// search objectlist

View File

@@ -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<ObjectVolumeID> 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;
}
}

View File

@@ -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) {

View File

@@ -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 = "");

View File

@@ -4507,7 +4507,7 @@ struct Plater::priv
// BBS: backup & restore
std::vector<size_t> load_files(const std::vector<fs::path>& input_files, LoadStrategy strategy, bool ask_multi = false);
std::vector<size_t> load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false, bool split_object = false);
std::vector<size_t> 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<size_t> Plater::priv::load_files(const std::vector<fs::path>& 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<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
#define AUTOPLACEMENT_ON_LOAD
std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z, bool split_object)
std::vector<size_t> 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<size_t> 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<size_t> 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<size_t> 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<size_t> 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<size_t> 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<size_t> idxs = load_model_objects(new_objects, false, true);
std::vector<size_t> 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();

View File

@@ -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

View File

@@ -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<std::pair<int, int>> 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<int, int>& 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<std::pair<int, int>> 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<std::pair<int, int>> 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<float>();
const Vec3f b_max = box.max.cast<float>();
const Vec3f size = 0.2f * box.size().cast<float>();
// 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<Vec3f> 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<int, int> 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<int, int> 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) {

View File

@@ -181,6 +181,7 @@ private:
GLModel m_arrow;
GLModel m_curved_arrow;
GLModel m_box;
bool m_auto_drop = true;
struct Planes
{
std::array<Vec3f, 2> 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