diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index bbedc0802..c5ede9792 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -362,19 +362,18 @@ const BoundingBoxf3& GLVolume::transformed_bounding_box() const const BoundingBoxf3& GLVolume::transformed_convex_hull_bounding_box() const { - if (m_transformed_convex_hull_bounding_box_dirty) - { - if ((m_convex_hull != nullptr) && (m_convex_hull->stl.stats.number_of_facets > 0)) - m_transformed_convex_hull_bounding_box = m_convex_hull->transformed_bounding_box(world_matrix()); - else - m_transformed_convex_hull_bounding_box = bounding_box.transformed(world_matrix()); - - m_transformed_convex_hull_bounding_box_dirty = false; - } - + if (m_transformed_convex_hull_bounding_box_dirty) + m_transformed_convex_hull_bounding_box = this->transformed_convex_hull_bounding_box(world_matrix()); return m_transformed_convex_hull_bounding_box; } +BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d &trafo) const +{ + return (m_convex_hull != nullptr && m_convex_hull->stl.stats.number_of_facets > 0) ? + m_convex_hull->transformed_bounding_box(trafo) : + bounding_box.transformed(trafo); +} + void GLVolume::set_range(double min_z, double max_z) { this->qverts_range.first = 0; diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index faf5a745d..191b6a016 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -397,6 +397,9 @@ public: bool is_left_handed() const; const BoundingBoxf3& transformed_bounding_box() const; + // non-caching variant + BoundingBoxf3 transformed_convex_hull_bounding_box(const Transform3d &trafo) const; + // caching variant const BoundingBoxf3& transformed_convex_hull_bounding_box() const; bool empty() const { return this->indexed_vertex_array.empty(); } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 79d6b7d2e..09ef2b2ac 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1748,7 +1748,7 @@ void GLCanvas3D::mirror_selection(Axis axis) { m_selection.mirror(axis); do_mirror(); - wxGetApp().obj_manipul()->update_settings_value(m_selection); + wxGetApp().obj_manipul()->set_dirty(); } // Reload the 3D scene of @@ -2091,7 +2091,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // to force a reset of its cache auto manip = wxGetApp().obj_manipul(); if (manip != nullptr) - manip->update_settings_value(m_selection); + manip->set_dirty(); } // and force this canvas to be redrawn. @@ -2726,7 +2726,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_regenerate_volumes = false; m_selection.translate(cur_pos - m_mouse.drag.start_position_3D); - wxGetApp().obj_manipul()->update_settings_value(m_selection); + wxGetApp().obj_manipul()->set_dirty(); m_dirty = true; } @@ -2781,7 +2781,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) { m_regenerate_volumes = false; do_move(); - wxGetApp().obj_manipul()->update_settings_value(m_selection); + wxGetApp().obj_manipul()->set_dirty(); // Let the platter know that the dragging finished, so a delayed refresh // of the scene with the background processing data should be performed. post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); @@ -2793,7 +2793,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) { m_selection.clear(); m_selection.set_mode(Selection::Instance); - wxGetApp().obj_manipul()->update_settings_value(m_selection); + wxGetApp().obj_manipul()->set_dirty(); m_gizmos.reset_all_states(); m_gizmos.update_data(*this); post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); @@ -2817,7 +2817,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_gizmos.refresh_on_off_state(m_selection); post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); m_gizmos.update_data(*this); - wxGetApp().obj_manipul()->update_settings_value(m_selection); + wxGetApp().obj_manipul()->set_dirty(); // forces a frame render to update the view before the context menu is shown render(); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 174879adc..1da17f676 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -197,8 +197,10 @@ bool ObjectManipulation::IsShown() void ObjectManipulation::UpdateAndShow(const bool show) { - if (show) - update_settings_value(wxGetApp().plater()->canvas3D()->get_selection()); + if (show) { + this->set_dirty(); + this->update_if_dirty(); + } OG_Settings::UpdateAndShow(show); } @@ -210,41 +212,35 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_scale_label_string = L("Scale factors"); ObjectList* obj_list = wxGetApp().obj_list(); - if (selection.is_single_full_instance() && ! m_world_coordinates) + if (selection.is_single_full_instance()) { // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); m_new_position = volume->get_instance_offset(); - m_new_rotation = volume->get_instance_rotation() * (180. / M_PI); - m_new_scale = volume->get_instance_scaling_factor() * 100.; - int obj_idx = volume->object_idx(); - int instance_idx = volume->instance_idx(); - if ((0 <= obj_idx) && (obj_idx < (int)wxGetApp().model_objects()->size())) - { - bool changed_box = false; - //FIXME matching an object idx may not be enough - if (!m_cache.instance.matches_object(obj_idx)) - { - m_cache.instance.set(obj_idx, instance_idx, (*wxGetApp().model_objects())[obj_idx]->raw_mesh_bounding_box().size()); - changed_box = true; - } - //FIXME matching an instance idx may not be enough. Check for ModelObject id an all ModelVolume ids. - if (changed_box || !m_cache.instance.matches_instance(instance_idx) || !m_cache.scale.isApprox(m_new_scale)) - m_new_size = volume->get_instance_transformation().get_scaling_factor().cwiseProduct(m_cache.instance.box_size); - } - else { - // this should never happen - assert(false); - m_new_size = Vec3d::Zero(); - } + + // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. + if (m_world_coordinates && ! m_uniform_scale && + ! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) { + // Manipulating an instance in the world coordinate system, rotation is not multiples of ninety degrees, therefore enforce uniform scaling. + m_uniform_scale = true; + m_lock_bnt->SetLock(true); + } + + if (m_world_coordinates) { + m_new_rotate_label_string = L("Rotate"); + m_new_rotation = Vec3d::Zero(); + m_new_size = selection.get_bounding_box().size(); + m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.; + } else { + m_new_rotation = volume->get_instance_rotation() * (180. / M_PI); + m_new_size = volume->get_instance_transformation().get_scaling_factor().cwiseProduct((*wxGetApp().model_objects())[volume->object_idx()]->raw_mesh_bounding_box().size()); + m_new_scale = volume->get_instance_scaling_factor() * 100.; + } m_new_enabled = true; } - else if ((selection.is_single_full_instance() && m_world_coordinates) || - (selection.is_single_full_object() && obj_list->is_selected(itObject))) + else if (selection.is_single_full_object() && obj_list->is_selected(itObject)) { - m_cache.instance.reset(); - const BoundingBoxf3& box = selection.get_bounding_box(); m_new_position = box.center(); m_new_rotation = Vec3d::Zero(); @@ -253,23 +249,9 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_rotate_label_string = L("Rotate"); m_new_scale_label_string = L("Scale"); m_new_enabled = true; - - if (selection.is_single_full_instance() && m_world_coordinates && ! m_uniform_scale) { - // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. - // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - // Is the angle close to a multiple of 90 degrees? - if (! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) { - // Manipulating an instance in the world coordinate system, rotation is not multiples of ninety degrees, therefore enforce uniform scaling. - m_uniform_scale = true; - m_lock_bnt->SetLock(true); - } - } } else if (selection.is_single_modifier() || selection.is_single_volume()) { - m_cache.instance.reset(); - // the selection contains a single volume const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); m_new_position = volume->get_volume_offset(); @@ -292,15 +274,16 @@ void ObjectManipulation::update_settings_value(const Selection& selection) // assert(selection.is_empty()); reset_settings_value(); } - - m_dirty = true; } void ObjectManipulation::update_if_dirty() { - if (!m_dirty) + if (! m_dirty) return; + const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection(); + this->update_settings_value(selection); + auto update_label = [](wxString &label_cache, const std::string &new_label, wxStaticText *widget) { wxString new_label_localized = _(new_label) + ":"; if (label_cache != new_label_localized) { @@ -330,7 +313,7 @@ void ObjectManipulation::update_if_dirty() update(m_cache.rotation, m_cache.rotation_rounded, "rotation_", m_new_rotation); } - if (wxGetApp().plater()->canvas3D()->get_selection().requires_uniform_scale()) { + if (selection.requires_uniform_scale()) { m_lock_bnt->SetLock(true); m_lock_bnt->Disable(); } @@ -377,8 +360,9 @@ void ObjectManipulation::reset_settings_value() m_new_scale = Vec3d::Ones(); m_new_size = Vec3d::Zero(); m_new_enabled = false; - m_cache.instance.reset(); - m_dirty = true; + // no need to set the dirty flag here as this method is called from update_settings_value(), + // which is called from update_if_dirty(), which resets the dirty flag anyways. +// m_dirty = true; } void ObjectManipulation::change_position_value(int axis, double value) @@ -437,12 +421,9 @@ void ObjectManipulation::change_scale_value(int axis, double value) return; Vec3d scale = m_cache.scale; - scale(axis) = value; + scale(axis) = value; - this->do_scale(scale); - - if (!m_cache.scale.isApprox(scale)) - m_cache.instance.instance_idx = -1; + this->do_scale(axis, scale); m_cache.scale = scale; m_cache.scale_rounded(axis) = DBL_MAX; @@ -460,46 +441,34 @@ void ObjectManipulation::change_size_value(int axis, double value) const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); Vec3d ref_size = m_cache.size; - if (selection.is_single_volume() || selection.is_single_modifier()) - { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - ref_size = volume->bounding_box.size(); - } - else if (selection.is_single_full_instance() && ! m_world_coordinates) - ref_size = m_cache.instance.box_size; + if (selection.is_single_volume() || selection.is_single_modifier()) + ref_size = selection.get_volume(*selection.get_volume_idxs().begin())->bounding_box.size(); + else if (selection.is_single_full_instance()) + ref_size = m_world_coordinates ? + selection.get_unscaled_instance_bounding_box().size() : + (*wxGetApp().model_objects())[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); - this->do_scale(100. * Vec3d(size(0) / ref_size(0), size(1) / ref_size(1), size(2) / ref_size(2))); + this->do_scale(axis, 100. * Vec3d(size(0) / ref_size(0), size(1) / ref_size(1), size(2) / ref_size(2))); m_cache.size = size; m_cache.size_rounded(axis) = DBL_MAX; this->UpdateAndShow(true); } -void ObjectManipulation::do_scale(const Vec3d &scale) const +void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const { Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); Vec3d scaling_factor = scale; TransformationType transformation_type(TransformationType::World_Relative_Joint); if (selection.is_single_full_instance()) { - if (m_world_coordinates) { - // Only a 90 degree rotation is allowed, therefore an axis aligned scaling will - // be still axis aligned after the instance rotation is applied. - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - scaling_factor = (volume->get_instance_transformation().get_matrix(true, false, true, true) * scale).cwiseAbs(); - // Absolute scaling shall not change. - assert(std::abs(scale.maxCoeff() - scaling_factor.maxCoeff()) < EPSILON); - assert(std::abs(scale.minCoeff() - scaling_factor.minCoeff()) < EPSILON); - assert(std::abs(scale.squaredNorm() - scaling_factor.squaredNorm()) < EPSILON); - } else + transformation_type.set_absolute(); + if (! m_world_coordinates) transformation_type.set_local(); } - if (m_uniform_scale || selection.requires_uniform_scale()) { - int max_diff_axis; - (scale - m_cache.scale).cwiseAbs().maxCoeff(&max_diff_axis); - scaling_factor = scale(max_diff_axis) * Vec3d::Ones(); - } + if (m_uniform_scale || selection.requires_uniform_scale()) + scaling_factor = scale(axis) * Vec3d::Ones(); selection.start_dragging(); selection.scale(scaling_factor * 0.01, transformation_type); @@ -577,8 +546,10 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value) if (! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) { // Cannot apply scaling in the world coordinate system. wxMessageDialog dlg(GUI::wxGetApp().mainframe, - _(L("Non-uniform scaling of tilted objects is not supported in the World coordinate system.\n" - "Do you want to rotate the mesh?")), + _(L("The currently manipulated object is tilted (rotation angles are not multiples of 90°).\n" + "Non-uniform scaling of tilted objects is only possible in the World coordinate system,\n" + "once the rotation is embedded into the object coordinates.\n" + "Do you want to proceed?")), SLIC3R_APP_NAME, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); if (dlg.ShowModal() != wxID_YES) { @@ -590,6 +561,8 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value) (*wxGetApp().model_objects())[volume->composite_id.object_id]->bake_xy_rotation_into_meshes(volume->composite_id.instance_id); // Update the 3D scene, selections etc. wxGetApp().plater()->update(); + // Recalculate cached values at this panel, refresh the screen. + this->UpdateAndShow(true); } } m_uniform_scale = new_value; diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index e93ce1ffb..833070e03 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -32,22 +32,6 @@ class ObjectManipulation : public OG_Settings wxString rotate_label_string; wxString scale_label_string; - struct Instance - { - int object_idx; - int instance_idx; - Vec3d box_size; - - Instance() { reset(); } - void reset() { this->object_idx = -1; this->instance_idx = -1; this->box_size = Vec3d::Zero(); } - void set(int object_idx, int instance_idx, const Vec3d& box_size) { this->object_idx = object_idx; this->instance_idx = instance_idx; this->box_size = box_size; } - bool matches(int object_idx, int instance_idx) const { return (this->object_idx == object_idx) && (this->instance_idx == instance_idx); } - bool matches_object(int object_idx) const { return (this->object_idx == object_idx); } - bool matches_instance(int instance_idx) const { return (this->instance_idx == instance_idx); } - }; - - Instance instance; - Cache() { reset(); } void reset() { @@ -58,7 +42,6 @@ class ObjectManipulation : public OG_Settings move_label_string = wxString(); rotate_label_string = wxString(); scale_label_string = wxString(); - instance.reset(); } bool is_valid() const { return position != Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); } }; @@ -99,8 +82,7 @@ public: bool IsShown() override; void UpdateAndShow(const bool show) override; - void update_settings_value(const Selection& selection); - + void set_dirty() { m_dirty = true; } // Called from the App to update the UI if dirty. void update_if_dirty(); @@ -120,6 +102,7 @@ public: private: void reset_settings_value(); + void update_settings_value(const Selection& selection); // update size values after scale unit changing or "gizmos" void update_size_value(const Vec3d& size); @@ -131,7 +114,7 @@ private: void change_rotation_value(int axis, double value); void change_scale_value(int axis, double value); void change_size_value(int axis, double value); - void do_scale(const Vec3d &scale) const; + void do_scale(int axis, const Vec3d &scale) const; void on_change(t_config_option_key opt_key, const boost::any& value); void on_fill_empty_value(const std::string& opt_key); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 90d6e6c6d..8e01806f1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -590,7 +590,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) // Rotate the object so the normal points downward: selection.flattening_rotate(get_flattening_normal()); canvas.do_flatten(); - wxGetApp().obj_manipul()->update_settings_value(selection); + wxGetApp().obj_manipul()->set_dirty(); } canvas.set_as_dirty(); @@ -623,17 +623,17 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) { // Apply new temporary offset selection.translate(get_displacement()); - wxGetApp().obj_manipul()->update_settings_value(selection); + wxGetApp().obj_manipul()->set_dirty(); break; } case Scale: { // Apply new temporary scale factors - TransformationType transformation_type(TransformationType::Local_Relative_Joint); + TransformationType transformation_type(TransformationType::Local_Absolute_Joint); if (evt.AltDown()) transformation_type.set_independent(); selection.scale(get_scale(), transformation_type); - wxGetApp().obj_manipul()->update_settings_value(selection); + wxGetApp().obj_manipul()->set_dirty(); break; } case Rotate: @@ -643,7 +643,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) if (evt.AltDown()) transformation_type.set_independent(); selection.rotate(get_rotation(), transformation_type); - wxGetApp().obj_manipul()->update_settings_value(selection); + wxGetApp().obj_manipul()->set_dirty(); break; } default: @@ -680,7 +680,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) stop_dragging(); update_data(canvas); - wxGetApp().obj_manipul()->update_settings_value(selection); + wxGetApp().obj_manipul()->set_dirty(); // Let the platter know that the dragging finished, so a delayed refresh // of the scene with the background processing data should be performed. canvas.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index e4912918d..28cab3eba 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -54,10 +54,10 @@ Selection::Selection() , m_mode(Instance) , m_type(Empty) , m_valid(false) - , m_bounding_box_dirty(true) , m_curved_arrow(16) , m_scale_factor(1.0f) { + this->set_bounding_boxes_dirty(); #if ENABLE_RENDER_SELECTION_CENTER m_quadric = ::gluNewQuadric(); if (m_quadric != nullptr) @@ -148,7 +148,7 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec } update_type(); - m_bounding_box_dirty = true; + this->set_bounding_boxes_dirty(); } void Selection::remove(unsigned int volume_idx) @@ -173,7 +173,7 @@ void Selection::remove(unsigned int volume_idx) } update_type(); - m_bounding_box_dirty = true; + this->set_bounding_boxes_dirty(); } void Selection::add_object(unsigned int object_idx, bool as_single_selection) @@ -190,7 +190,7 @@ void Selection::add_object(unsigned int object_idx, bool as_single_selection) do_add_object(object_idx); update_type(); - m_bounding_box_dirty = true; + this->set_bounding_boxes_dirty(); } void Selection::remove_object(unsigned int object_idx) @@ -201,7 +201,7 @@ void Selection::remove_object(unsigned int object_idx) do_remove_object(object_idx); update_type(); - m_bounding_box_dirty = true; + this->set_bounding_boxes_dirty(); } void Selection::add_instance(unsigned int object_idx, unsigned int instance_idx, bool as_single_selection) @@ -218,7 +218,7 @@ void Selection::add_instance(unsigned int object_idx, unsigned int instance_idx, do_add_instance(object_idx, instance_idx); update_type(); - m_bounding_box_dirty = true; + this->set_bounding_boxes_dirty(); } void Selection::remove_instance(unsigned int object_idx, unsigned int instance_idx) @@ -229,7 +229,7 @@ void Selection::remove_instance(unsigned int object_idx, unsigned int instance_i do_remove_instance(object_idx, instance_idx); update_type(); - m_bounding_box_dirty = true; + this->set_bounding_boxes_dirty(); } void Selection::add_volume(unsigned int object_idx, unsigned int volume_idx, int instance_idx, bool as_single_selection) @@ -254,7 +254,7 @@ void Selection::add_volume(unsigned int object_idx, unsigned int volume_idx, int } update_type(); - m_bounding_box_dirty = true; + this->set_bounding_boxes_dirty(); } void Selection::remove_volume(unsigned int object_idx, unsigned int volume_idx) @@ -270,7 +270,7 @@ void Selection::remove_volume(unsigned int object_idx, unsigned int volume_idx) } update_type(); - m_bounding_box_dirty = true; + this->set_bounding_boxes_dirty(); } void Selection::add_all() @@ -288,7 +288,7 @@ void Selection::add_all() } update_type(); - m_bounding_box_dirty = true; + this->set_bounding_boxes_dirty(); } void Selection::clear() @@ -304,7 +304,7 @@ void Selection::clear() m_list.clear(); update_type(); - m_bounding_box_dirty = true; + this->set_bounding_boxes_dirty(); // resets the cache in the sidebar wxGetApp().obj_manipul()->reset_cache(); @@ -323,7 +323,7 @@ void Selection::instances_changed(const std::vector &instance_ids_select this->do_add_volume(volume_idx); } update_type(); - m_bounding_box_dirty = true; + this->set_bounding_boxes_dirty(); } // Update the selection based on the map from old indices to new indices after m_volumes changed. @@ -341,7 +341,7 @@ void Selection::volumes_changed(const std::vector &map_volume_old_to_new } m_list = std::move(list_new); update_type(); - m_bounding_box_dirty = true; + this->set_bounding_boxes_dirty(); } bool Selection::is_single_full_instance() const @@ -426,6 +426,14 @@ const BoundingBoxf3& Selection::get_bounding_box() const return m_bounding_box; } +const BoundingBoxf3& Selection::get_unscaled_instance_bounding_box() const +{ + if (m_unscaled_instance_bounding_box_dirty) + calc_unscaled_instance_bounding_box(); + + return m_unscaled_instance_bounding_box; +} + void Selection::start_dragging() { if (!m_valid) @@ -473,7 +481,7 @@ void Selection::translate(const Vec3d& displacement, bool local) synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH - m_bounding_box_dirty = true; + this->set_bounding_boxes_dirty(); } // Rotate an object around one of the axes. Only one rotation component is expected to be changing. @@ -580,7 +588,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH - m_bounding_box_dirty = true; + this->set_bounding_boxes_dirty(); } void Selection::flattening_rotate(const Vec3d& normal) @@ -621,7 +629,7 @@ void Selection::flattening_rotate(const Vec3d& normal) synchronize_unselected_instances(SYNC_ROTATION_FULL); #endif // !DISABLE_INSTANCES_SYNCH - m_bounding_box_dirty = true; + this->set_bounding_boxes_dirty(); } void Selection::scale(const Vec3d& scale, TransformationType transformation_type) @@ -629,15 +637,21 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type if (!m_valid) return; - // Only relative scaling values are allowed in the world coordinate system. - assert(! transformation_type.world() || transformation_type.relative()); - for (unsigned int i : m_list) { - if (is_single_full_instance() && ! transformation_type.world()) - (*m_volumes)[i]->set_instance_scaling_factor(scale); + GLVolume &volume = *(*m_volumes)[i]; + if (is_single_full_instance()) { + assert(transformation_type.absolute()); + if (transformation_type.world() && (std::abs(scale.x() - scale.y()) > EPSILON || std::abs(scale.x() - scale.z()) > EPSILON)) { + // Non-uniform scaling. Transform the scaling factors into the local coordinate system. + // This is only possible, if the instance rotation is mulitples of ninety degrees. + assert(Geometry::is_rotation_ninety_degrees(volume.get_instance_rotation())); + volume.set_instance_scaling_factor((volume.get_instance_transformation().get_matrix(true, false, true, true).matrix().block<3, 3>(0, 0).transpose() * scale).cwiseAbs()); + } else + volume.set_instance_scaling_factor(scale); + } else if (is_single_volume() || is_single_modifier()) - (*m_volumes)[i]->set_volume_scaling_factor(scale); + volume.set_volume_scaling_factor(scale); else { Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); @@ -647,9 +661,9 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type // extracts scaling factors from the composed transformation Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); if (transformation_type.joint()) - (*m_volumes)[i]->set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); + volume.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); - (*m_volumes)[i]->set_instance_scaling_factor(new_scale); + volume.set_instance_scaling_factor(new_scale); } else if (m_mode == Volume) { @@ -659,9 +673,9 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type if (transformation_type.joint()) { Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); - (*m_volumes)[i]->set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset); + volume.set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset); } - (*m_volumes)[i]->set_volume_scaling_factor(new_scale); + volume.set_volume_scaling_factor(new_scale); } } } @@ -675,7 +689,7 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type ensure_on_bed(); - m_bounding_box_dirty = true; + this->set_bounding_boxes_dirty(); } void Selection::mirror(Axis axis) @@ -700,7 +714,7 @@ void Selection::mirror(Axis axis) synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH - m_bounding_box_dirty = true; + this->set_bounding_boxes_dirty(); } void Selection::translate(unsigned int object_idx, const Vec3d& displacement) @@ -745,7 +759,7 @@ void Selection::translate(unsigned int object_idx, const Vec3d& displacement) } } - m_bounding_box_dirty = true; + this->set_bounding_boxes_dirty(); } void Selection::translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement) @@ -790,7 +804,7 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co } } - m_bounding_box_dirty = true; + this->set_bounding_boxes_dirty(); } void Selection::erase() @@ -1396,7 +1410,23 @@ void Selection::calc_bounding_box() const m_bounding_box.merge((*m_volumes)[i]->transformed_convex_hull_bounding_box()); } } - m_bounding_box_dirty = false; + m_bounding_box_dirty = false; +} + +void Selection::calc_unscaled_instance_bounding_box() const +{ + m_unscaled_instance_bounding_box = BoundingBoxf3(); + if (m_valid) + { + for (unsigned int i : m_list) + { + const GLVolume &volume = *(*m_volumes)[i]; + Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, true, false) * volume.get_volume_transformation().get_matrix(); + trafo.translation()(2) += volume.get_sla_shift_z(); + m_unscaled_instance_bounding_box.merge(volume.transformed_convex_hull_bounding_box(trafo)); + } + } + m_unscaled_instance_bounding_box_dirty = false; } void Selection::render_selected_volumes() const diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 9f9c1c325..d808bee07 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -183,6 +183,10 @@ private: Clipboard m_clipboard; mutable BoundingBoxf3 m_bounding_box; mutable bool m_bounding_box_dirty; + // Bounding box of a selection, with no instance scaling applied. This bounding box + // is useful for absolute scaling of tilted objects in world coordinate space. + mutable BoundingBoxf3 m_unscaled_instance_bounding_box; + mutable bool m_unscaled_instance_bounding_box_dirty; #if ENABLE_RENDER_SELECTION_CENTER GLUquadricObj* m_quadric; @@ -265,6 +269,9 @@ public: unsigned int volumes_count() const { return (unsigned int)m_list.size(); } const BoundingBoxf3& get_bounding_box() const; + // Bounding box of a selection, with no instance scaling applied. This bounding box + // is useful for absolute scaling of tilted objects in world coordinate space. + const BoundingBoxf3& get_unscaled_instance_bounding_box() const; void start_dragging(); @@ -303,6 +310,8 @@ private: void do_remove_instance(unsigned int object_idx, unsigned int instance_idx); void do_remove_object(unsigned int object_idx); void calc_bounding_box() const; + void calc_unscaled_instance_bounding_box() const; + void set_bounding_boxes_dirty() { m_bounding_box_dirty = true; m_unscaled_instance_bounding_box_dirty = true; } void render_selected_volumes() const; void render_synchronized_volumes() const; void render_bounding_box(const BoundingBoxf3& box, float* color) const;