diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 3ac4fc2f78..88dc228ac5 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5588,13 +5588,14 @@ void GLCanvas3D::do_move(const std::string& snapshot_type, bool force_volume_mov // parts moved. Notifying a plate about an instance that stayed put invalidates its slice // result, and notifying instance 0 alone left a moved copy unregistered on its new plate. { + const bool parts_moved = force_volume_move || selection_mode == Selection::Volume; std::set> notified; for (unsigned int i : m_selection.get_volume_idxs()) { const GLVolume* v = m_volumes.volumes[i]; const int object_idx = v->object_idx(); if (object_idx < 0 || object_idx >= static_cast(m_model->objects.size())) continue; - const std::pair key(object_idx, selection_mode == Selection::Volume ? -1 : v->instance_idx()); + const std::pair key(object_idx, parts_moved ? -1 : v->instance_idx()); if (notified.insert(key).second) m_selection.notify_instance_update(key.first, key.second); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAlignment.cpp b/src/slic3r/GUI/Gizmos/GLGizmoAlignment.cpp index 7e97d64940..3b1c5f350e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAlignment.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAlignment.cpp @@ -1,8 +1,19 @@ #include "GLGizmoAlignment.hpp" +#include "slic3r/GUI/3DScene.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/PartPlate.hpp" #include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/Selection.hpp" +#include "libslic3r/BoundingBox.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/Point.hpp" + +#include +#include +#include +#include +#include namespace Slic3r { namespace GUI { @@ -13,7 +24,7 @@ GLGizmoAlignment::GLGizmoAlignment(GLCanvas3D& canvas) : m_canvas(canvas) bool GLGizmoAlignment::align_objects(AlignType type, bool align_parent) { - if (!validate_selection_for_align()) { + if (!can_align(type, align_parent)) { return false; } @@ -92,32 +103,29 @@ bool GLGizmoAlignment::align_to_x_min(bool align_parent) bool GLGizmoAlignment::align_to_center(AlignType type,bool align_parent) { - const Selection& selection = get_selection(); std::string type_str = type == AlignType::CENTER_X ? "X" : (type == AlignType::CENTER_Y ? "Y" : "Z"); std::string operation_name = type == AlignType::CENTER_X ? "X Center" : (type == AlignType::CENTER_Y ? "Y Center" : "Z Center"); double selection_center = 0.f; if (align_parent) { - selection_center = get_current_coord(operation_name, m_parent_box); + BoundingBoxf3 parent_box = get_parent_box(); + selection_center = get_current_coord(operation_name, parent_box); // Handle multiple volume selection - if (is_part_align_parent()) { + if (is_parts_selection(true)) { if (!take_snapshot("Align to parent center " + type_str)) { return false; } get_selection().setup_cache(); - auto selection_bbox = selection.get_bounding_box(); - double current_coord = get_current_coord(operation_name, selection_bbox); - Vec3d displacement = generate_displacement(operation_name, selection_center - current_coord); - // Move each selected volume to align to the center of their collective bounding box - for (unsigned int idx : selection.get_volume_idxs()) { - const GLVolume *volume = selection.get_volume(idx); - if (volume) { - get_selection().translate(volume->object_idx(), volume->instance_idx(), volume->volume_idx(), displacement); - } + BoundingBoxf3 selection_bbox = get_items_box(); + double current_coord = get_current_coord(operation_name, selection_bbox); + Vec3d displacement = generate_displacement(operation_name, selection_center - current_coord); + // Move the selected volumes together so that their collective bounding box is centered in the parent + for (const GLVolume *volume : get_selected_part_volumes()) { + get_selection().translate(volume->object_idx(), volume->instance_idx(), volume->volume_idx(), displacement, false); } - finish_operation("Align to parent center " + type_str); + finish_operation(true); return true; } BoundingBoxf3 big_bb; @@ -125,7 +133,6 @@ bool GLGizmoAlignment::align_to_center(AlignType type,bool align_parent) if (objects.empty()) return false; // Handle multiple objects or parts selection - BoundingBoxf3 selection_bbox = selection.get_bounding_box(); if (!take_snapshot("Align to parent center " + type_str)) { return false; } @@ -138,60 +145,52 @@ bool GLGizmoAlignment::align_to_center(AlignType type,bool align_parent) apply_transformation(obj.object_idx, obj.instance_idx, displacement); } - finish_operation("Align to parent center " + type_str); + finish_operation(); return true; } - // Handle single object selection// Handle multiple volume selection - if (selection.is_single_full_object() || - selection.is_multiple_volume() || selection.is_multiple_modifier() || selection.is_single_volume() || selection.is_single_modifier()) { + // Handle the parts of a single instance + if (is_parts_selection(false)) { if (!take_snapshot("Align Parts Center " + type_str)){ return false; } get_selection().setup_cache(); - BoundingBoxf3 selection_bbox = selection.get_bounding_box(); + BoundingBoxf3 selection_bbox = get_items_box(); selection_center = get_current_coord(operation_name, selection_bbox); - // Move each selected volume to align to the center of their collective bounding box - for (unsigned int idx : selection.get_volume_idxs()) { - const GLVolume* volume = selection.get_volume(idx); - if (volume) { - auto temp_box = volume->transformed_convex_hull_bounding_box(); - double current = get_current_coord(operation_name, temp_box); - double offset = selection_center - current; - Vec3d displacement = generate_displacement(operation_name, offset); - get_selection().translate(volume->object_idx(), volume->instance_idx(), volume->volume_idx(), displacement); - } + // Move each part to align to the center of their collective bounding box + for (const GLVolume *volume : get_selected_part_volumes()) { + auto temp_box = volume->transformed_convex_hull_bounding_box(); + double current = get_current_coord(operation_name, temp_box); + double offset = selection_center - current; + Vec3d displacement = generate_displacement(operation_name, offset); + get_selection().translate(volume->object_idx(), volume->instance_idx(), volume->volume_idx(), displacement, false); } - finish_operation("Align Parts Center" + type_str, true); + finish_operation(true); return true; } BoundingBoxf3 big_bb; auto objects = get_selected_objects_info(big_bb); if (objects.empty()) return false; - // Handle multiple objects or parts selection - if (selection.is_multiple_full_object()) { - BoundingBoxf3 selection_bbox = selection.get_bounding_box(); - double selection_center = get_current_coord(operation_name, selection_bbox); + // Handle multiple instances + selection_center = get_current_coord(operation_name, big_bb); - if (!take_snapshot("Align Objects Center" + type_str)) { - return false; - } - - get_selection().setup_cache(); - // Multiple objects: use selection bounding box for alignment - for (const auto& obj : objects) { - BoundingBoxf3 temp_bbox = obj.bbox; - double current_coord = get_current_coord(operation_name, temp_bbox); - Vec3d displacement = generate_displacement(operation_name, selection_center - current_coord); - apply_transformation(obj.object_idx, obj.instance_idx, displacement); - } - finish_operation("Align Objects Center" + type_str); - return true; + if (!take_snapshot("Align Objects Center" + type_str)) { + return false; } - return false; + + get_selection().setup_cache(); + // Multiple objects: use selection bounding box for alignment + for (const auto& obj : objects) { + BoundingBoxf3 temp_bbox = obj.bbox; + double current_coord = get_current_coord(operation_name, temp_bbox); + Vec3d displacement = generate_displacement(operation_name, selection_center - current_coord); + apply_transformation(obj.object_idx, obj.instance_idx, displacement); + } + finish_operation(); + return true; } bool GLGizmoAlignment::align_to_z_max(bool align_parent) @@ -244,37 +243,31 @@ bool GLGizmoAlignment::distribute_z() template bool GLGizmoAlignment::align_objects_generic(GetCoordFunc get_coord, SetCoordFunc set_coord, const std::string &operation_name ,bool align_parent) { - const Selection& selection = get_selection(); if (align_parent) { // Find reference coordinate - double reference_coord = get_current_coord(operation_name, m_parent_box); + BoundingBoxf3 parent_box = get_parent_box(); + double reference_coord = get_current_coord(operation_name, parent_box); // Handle parts selection differently - if (is_part_align_parent()) { + if (is_parts_selection(true)) { if (!take_snapshot("align to parent node " + operation_name)) { return false; } get_selection().setup_cache(); - // Get all selected volumes - std::vector volumes; - for (unsigned int idx : selection.get_volume_idxs()) { - const GLVolume *volume = selection.get_volume(idx); - if (volume) { volumes.push_back(volume); } - } - + const std::vector volumes = get_selected_part_volumes(); if (volumes.empty()) return false; - BoundingBoxf3 selection_bbox = selection.get_bounding_box(); + BoundingBoxf3 selection_bbox = get_items_box(); double current_coord = get_current_coord(operation_name, selection_bbox); Vec3d displacement = generate_displacement(operation_name, reference_coord - current_coord); for (const GLVolume *volume : volumes) { if (displacement.norm() > 1e-6) { - get_selection().translate(volume->object_idx(), volume->instance_idx(), volume->volume_idx(), displacement); + get_selection().translate(volume->object_idx(), volume->instance_idx(), volume->volume_idx(), displacement, false); } } - finish_operation("align to parent node " + operation_name); + finish_operation(true); return true; } @@ -296,26 +289,18 @@ bool GLGizmoAlignment::align_objects_generic(GetCoordFunc get_coord, SetCoordFun } } - finish_operation("align to parent node " + operation_name); + finish_operation(); return true; } // Handle parts selection differently - if (selection.is_single_full_object() || selection.is_multiple_volume() || selection.is_multiple_modifier()) { + if (is_parts_selection(false)) { if (!take_snapshot(operation_name)) { return false; } get_selection().setup_cache(); - // Get all selected volumes - std::vector volumes; - for (unsigned int idx : selection.get_volume_idxs()) { - const GLVolume* volume = selection.get_volume(idx); - if (volume) { - volumes.push_back(volume); - } - } - + const std::vector volumes = get_selected_part_volumes(); if (volumes.empty()) return false; // Find reference coordinate @@ -381,11 +366,11 @@ bool GLGizmoAlignment::align_objects_generic(GetCoordFunc get_coord, SetCoordFun } if (displacement.norm() > 1e-6) { - get_selection().translate(volume->object_idx(), volume->instance_idx(), volume->volume_idx(), displacement); + get_selection().translate(volume->object_idx(), volume->instance_idx(), volume->volume_idx(), displacement, false); } } - finish_operation(operation_name,true); + finish_operation(true); return true; } @@ -420,22 +405,15 @@ bool GLGizmoAlignment::align_objects_generic(GetCoordFunc get_coord, SetCoordFun } } - finish_operation(operation_name); + finish_operation(); return true; } template bool GLGizmoAlignment::distribute_objects_generic(GetCoordFunc get_coord, int axis, const std::string& operation_name) { - const Selection& selection = get_selection(); - - if (selection.is_single_full_object() || selection.is_multiple_volume() || selection.is_multiple_modifier()) { - std::vector volumes; - for (unsigned int idx : selection.get_volume_idxs()) { - const GLVolume* v = selection.get_volume(idx); - if (v) volumes.push_back(v); - } - + if (is_parts_selection(false)) { + const std::vector volumes = get_selected_part_volumes(); if (volumes.size() < 3) return false; if (!take_snapshot(operation_name)) { return false; @@ -454,7 +432,7 @@ bool GLGizmoAlignment::distribute_objects_generic(GetCoordFunc get_coord, int ax std::sort(entries.begin(), entries.end(), [](const VolEntry& a, const VolEntry& b){ return a.coord < b.coord; }); - BoundingBoxf3 selection_bbox = selection.get_bounding_box(); + BoundingBoxf3 selection_bbox = get_items_box(); BoundingBoxf3 left_bbox = entries.front().v->transformed_convex_hull_bounding_box(); BoundingBoxf3 right_bbox = entries.back().v->transformed_convex_hull_bounding_box(); auto axis_extent = [&](const BoundingBoxf3 &bb) { return (axis == 0 ? (bb.max.x() - bb.min.x()) : axis == 1 ? (bb.max.y() - bb.min.y()) : (bb.max.z() - bb.min.z())); }; @@ -475,11 +453,11 @@ bool GLGizmoAlignment::distribute_objects_generic(GetCoordFunc get_coord, int ax Vec3d displacement = Vec3d::Zero(); displacement[axis] = offset; const GLVolume *v = entries[i].v; - get_selection().translate(v->object_idx(), v->instance_idx(), v->volume_idx(), displacement); + get_selection().translate(v->object_idx(), v->instance_idx(), v->volume_idx(), displacement, false); } } - finish_operation(operation_name,true); + finish_operation(true); return true; } BoundingBoxf3 big_bb; @@ -513,52 +491,19 @@ bool GLGizmoAlignment::distribute_objects_generic(GetCoordFunc get_coord, int ax } } - finish_operation(operation_name); + finish_operation(); return true; } -bool GLGizmoAlignment::can_align(AlignType type) const +bool GLGizmoAlignment::can_align(AlignType type, bool align_parent) const { - const Selection& selection = get_selection(); - - bool is_single_object = selection.is_single_full_object(); - bool is_multiple_objects = selection.is_multiple_full_object(); - bool is_single_part = selection.is_single_volume() || selection.is_single_modifier(); - bool is_multiple_parts = selection.is_multiple_volume() || selection.is_multiple_modifier(); - - - return validate_selection_for_align(); + return get_items_count(type, align_parent) >= (align_parent ? 1 : 2); } bool GLGizmoAlignment::can_distribute(AlignType type) const { - const Selection& selection = get_selection(); - if (selection.is_single_full_object()) { - size_t count = selection.get_volume_idxs().size(); - if (count >= 3) - return true; - } - if (selection.is_single_volume() || - selection.is_single_modifier()) { - return false; - } - - if (selection.is_multiple_volume() || selection.is_multiple_modifier()) { - size_t count = selection.get_volume_idxs().size(); - if (count < 3) return false; - return (type == AlignType::DISTRIBUTE_X || - type == AlignType::DISTRIBUTE_Y || - type == AlignType::DISTRIBUTE_Z); - } - - if (selection.is_multiple_full_object()) { - BoundingBoxf3 big_bb; - auto objects = get_selected_objects_info(big_bb); - if (objects.size() < 3) return false; - return (type == AlignType::DISTRIBUTE_X || type == AlignType::DISTRIBUTE_Y || type == AlignType::DISTRIBUTE_Z); - } - - return validate_selection_for_distribute(); + return (type == AlignType::DISTRIBUTE_X || type == AlignType::DISTRIBUTE_Y || type == AlignType::DISTRIBUTE_Z) && + get_items_count(type, false) >= 3; } double GLGizmoAlignment::get_current_coord(std::string operation_name, BoundingBoxf3 &bbox) @@ -598,15 +543,6 @@ Vec3d GLGizmoAlignment::generate_displacement(std::string operation_name, double return displacement; } -bool GLGizmoAlignment::is_part_align_parent() -{ - Selection &selection = get_selection(); - if (selection.is_multiple_volume() || selection.is_multiple_modifier() || selection.is_single_volume() || selection.is_single_modifier()) { - return true; - } - return false; -} - std::vector GLGizmoAlignment::get_selected_objects_info(BoundingBoxf3 &big_bb) const { std::vector objects; @@ -633,8 +569,72 @@ std::vector GLGizmoAlignment::get_selected_objects return objects; } -void GLGizmoAlignment::set_parent_box(const BoundingBoxf3 &bb) { - m_parent_box = bb; +bool GLGizmoAlignment::is_parts_selection(bool align_parent) const +{ + const Selection &selection = get_selection(); + const auto &content = selection.get_content(); + if (content.size() != 1 || content.begin()->first >= 1000 || content.begin()->second.size() != 1) + return false; + return !align_parent || selection.get_mode() == Selection::Volume; +} + +std::vector GLGizmoAlignment::get_selected_part_volumes() const +{ + std::vector volumes; + if (!is_parts_selection(false)) + return volumes; + + const Selection &selection = get_selection(); + const ModelObject *object = selection.get_model()->objects[selection.get_object_idx()]; + if (object->is_cut()) + return volumes; + + for (unsigned int idx : selection.get_volume_idxs()) { + const GLVolume *volume = selection.get_volume(idx); + if (volume->volume_idx() < 0) + continue; + // Moving a whole instance's parts leaves its modifiers, negative volumes and support blockers in place, + // as dragging a part does. + if (selection.get_mode() == Selection::Volume || object->volumes[volume->volume_idx()]->is_model_part()) + volumes.push_back(volume); + } + return volumes; +} + +BoundingBoxf3 GLGizmoAlignment::get_items_box() const +{ + BoundingBoxf3 box; + if (is_parts_selection(false)) { + for (const GLVolume *volume : get_selected_part_volumes()) + box.merge(volume->transformed_convex_hull_bounding_box()); + } else + get_selected_objects_info(box); + return box; +} + +BoundingBoxf3 GLGizmoAlignment::get_parent_box() const +{ + const Selection &selection = get_selection(); + if (is_parts_selection(true)) + return selection.get_model()->objects[selection.get_object_idx()]->instance_convex_hull_bounding_box(selection.get_instance_idx()); + + // can_align() requires every selected instance on one plate, so the first one gives the plate. + BoundingBoxf3 box; + PartPlateList &plate_list = wxGetApp().plater()->get_partplate_list(); + for (const auto &[obj_idx, inst_idxs] : selection.get_content()) { + if (obj_idx >= 1000 || inst_idxs.empty()) + continue; + PartPlate *plate = plate_list.get_plate(plate_list.find_instance(obj_idx, *inst_idxs.begin())); + if (plate == nullptr) + break; + box = plate->get_plate_box(); + box.max.z() = get_items_box().max.z(); + // Keep the aligned objects off the plate edge. + box.min += Vec3d(0.1, 0.1, 0.1); + box.max -= Vec3d(0.1, 0.1, 0.1); + break; + } + return box; } Selection& GLGizmoAlignment::get_selection() const @@ -653,41 +653,53 @@ void GLGizmoAlignment::apply_transformation(int obj_idx, int inst_idx, const Vec get_selection().translate(obj_idx, inst_idx, displacement); } -void GLGizmoAlignment::finish_operation(const std::string &operation_name, bool force_volume_move) +void GLGizmoAlignment::finish_operation(bool force_volume_move) { - get_selection().notify_instance_update(-1, -1); - m_canvas.do_move(operation_name, force_volume_move); + // A part move changes the ModelVolume shared by every copy of the object, so the other copies' volumes must show + // it too, or do_move() would write their stale transformation back to the model. + if (force_volume_move) + get_selection().synchronize_unselected_volumes(); + // The operation already took its snapshot. + m_canvas.do_move("", force_volume_move); } -bool GLGizmoAlignment::validate_selection_for_align() const +size_t GLGizmoAlignment::get_items_count(AlignType type, bool align_parent) const { - const Selection& selection = get_selection(); + if (is_parts_selection(align_parent)) + return get_selected_part_volumes().size(); - if (selection.is_single_full_object() || - selection.is_single_volume() || - selection.is_single_modifier()) { - return true; - } + // Instances are moved through their instance offsets, which do_move() commits only from an Instance selection. + const Selection &selection = get_selection(); + if (selection.get_mode() != Selection::Instance) + return 0; - return selection.get_volume_idxs().size() >= 2; -} + const bool is_z = type == AlignType::Z_MIN || type == AlignType::CENTER_Z || type == AlignType::Z_MAX || + type == AlignType::DISTRIBUTE_Z; + // The plate gives no Z target, and aligning to its bottom would duplicate Drop. + if (is_z && align_parent) + return 0; -bool GLGizmoAlignment::validate_selection_for_distribute() const -{ - const Selection& selection = get_selection(); - - if (selection.is_single_full_object() || selection.is_multiple_full_object()) { - std::set object_indices; - for (int volume_idx : selection.get_volume_idxs()) { - const GLVolume* volume = selection.get_volume(volume_idx); - if (volume) { - object_indices.insert(volume->object_idx()); + PartPlateList &plate_list = wxGetApp().plater()->get_partplate_list(); + int plate_idx = -1; + size_t count = 0; + for (const auto &[obj_idx, inst_idxs] : selection.get_content()) { + if (obj_idx >= 1000) + continue; + // Auto-drop would put a raised object back on the bed and leave a lowered one sunk. + if (is_z && selection.get_model()->objects[obj_idx]->auto_drop) + return 0; + if (align_parent) { + // The plate holding the selection is the target, so it must hold all of it. + for (int inst_idx : inst_idxs) { + const int idx = plate_list.find_instance(obj_idx, inst_idx); + if (idx == -1 || (plate_idx != -1 && idx != plate_idx)) + return 0; + plate_idx = idx; } } - return object_indices.size() >= 3; + count += inst_idxs.size(); } - - return selection.get_volume_idxs().size() >= 3; + return count; } } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAlignment.hpp b/src/slic3r/GUI/Gizmos/GLGizmoAlignment.hpp index 5bbeceb7a6..5300cc3a99 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAlignment.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAlignment.hpp @@ -4,8 +4,9 @@ #include "libslic3r/Point.hpp" #include "libslic3r/BoundingBox.hpp" #include "slic3r/GUI/Selection.hpp" +#include +#include #include -#include namespace Slic3r { namespace GUI { @@ -58,14 +59,24 @@ public: bool distribute_y(); bool distribute_z(); - bool can_align(AlignType type) const; + bool can_align(AlignType type, bool align_parent = false) const; bool can_distribute(AlignType type) const; std::vector get_selected_objects_info(BoundingBoxf3 &big_bb) const; double get_current_coord(std::string operation_name, BoundingBoxf3 &bbox); Vec3d generate_displacement(std::string operation_name, double offset); - bool is_part_align_parent(); - void set_parent_box(const BoundingBoxf3 &bb); + + // A parts selection (one instance of one object) aligns the parts inside it; anything else aligns whole instances. + // With align_parent, only a Volume-mode selection counts as parts: a whole instance aligns to its plate instead. + bool is_parts_selection(bool align_parent) const; + // The volumes a parts operation moves: the selected volumes, or only the model parts when a whole instance is + // selected. Empty for a cut object, whose parts must keep their place. + std::vector get_selected_part_volumes() const; + // World bounding box of the items an operation moves, the reference of every non-parent operation. + BoundingBoxf3 get_items_box() const; + // World bounding box aligned to with align_parent: the instance of a parts selection, or the plate holding the + // selected instances, up to their top. + BoundingBoxf3 get_parent_box() const; private: GLCanvas3D& m_canvas; @@ -80,13 +91,8 @@ private: Selection& get_selection() const; bool take_snapshot(const std::string& name); void apply_transformation(int obj_idx, int inst_idx, const Vec3d& displacement); - void finish_operation(const std::string &operation_name, bool force_volume_move = false); - - bool validate_selection_for_align() const; - bool validate_selection_for_distribute() const; - -private: - BoundingBoxf3 m_parent_box; + void finish_operation(bool force_volume_move = false); + size_t get_items_count(AlignType type, bool align_parent) const; }; } // namespace GUI diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index db27a05ac6..8978f165b9 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1757,50 +1757,27 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co //BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": obj %1%, instance %2%") % object_idx % instance_idx; //BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": %1%, displacement {%2%, %3%, %4%}") % __LINE__ % displacement(X) % displacement(Y) % displacement(Z); - for (unsigned int i : m_list) { - GLVolume& v = *(*m_volumes)[i]; - if (v.object_idx() == (int)object_idx && v.instance_idx() == (int)instance_idx) - v.set_instance_offset(v.get_instance_offset() + displacement); - } - - std::set done; // prevent processing volumes twice - done.insert(m_list.begin(), m_list.end()); - - for (unsigned int i : m_list) { - if (done.size() == m_volumes->size()) - break; - - int object_idx = (*m_volumes)[i]->object_idx(); - if (object_idx >= 1000) - continue; - - // Process unselected volumes of the object. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { - if (done.size() == m_volumes->size()) - break; - - if (done.find(j) != done.end()) - continue; - - GLVolume& v = *(*m_volumes)[j]; - if (v.object_idx() != object_idx || v.instance_idx() != (int)instance_idx) - continue; - - v.set_instance_offset(v.get_instance_offset() + displacement); - done.insert(j); - } + // Every volume of the instance, selected or not, so that the instance moves as a whole. + for (GLVolume* v : *m_volumes) { + if (v->object_idx() == (int)object_idx && v->instance_idx() == (int)instance_idx) + v->set_instance_offset(v->get_instance_offset() + displacement); } this->set_bounding_boxes_dirty(); } -void Selection::translate(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx, const Vec3d &displacement) { +void Selection::translate(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx, const Vec3d &displacement, bool local) +{ if (!m_valid) return; for (unsigned int i : m_list) { GLVolume &v = *(*m_volumes)[i]; - if (v.object_idx() == (int) object_idx && v.instance_idx() == (int) instance_idx && v.volume_idx() == (int) volume_idx) - v.set_volume_offset(v.get_volume_offset() + displacement); + if (v.object_idx() == (int) object_idx && v.instance_idx() == (int) instance_idx && v.volume_idx() == (int) volume_idx) { + Vec3d local_displacement = displacement; + if (!local) + local_displacement = v.get_instance_transformation().get_matrix_no_offset().inverse() * displacement; + v.set_volume_offset(v.get_volume_offset() + local_displacement); + } } this->set_bounding_boxes_dirty(); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 35ba184d08..c777c3c291 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -360,7 +360,8 @@ public: void translate(unsigned int object_idx, const Vec3d& displacement); void translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement); - void translate(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx, const Vec3d &displacement); + // local: true (default) if displacement is already in instance-local space; false treats it as world-space delta (L^-1 applied). + void translate(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx, const Vec3d &displacement, bool local = true); void rotate(unsigned int object_idx, unsigned int instance_idx, const Transform3d &overwrite_tran); void rotate(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx, const Transform3d &overwrite_tran);