diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index c821eef77..18a2d08c4 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -539,7 +539,7 @@ public: // 2D shape from top view. using ShapeData2D = std::vector>; -ShapeData2D projectModelFromTop(const Slic3r::Model &model) { +ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo& wti) { ShapeData2D ret; // Count all the items on the bin (all the object's instances) @@ -600,6 +600,28 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) { } } + // The wipe tower is a separate case (in case there is one), let's duplicate the code + if (wti.is_wipe_tower) { + Points pts; + pts.emplace_back(coord_t(scale_(0.)), coord_t(scale_(0.))); + pts.emplace_back(coord_t(scale_(wti.bb_size(0))), coord_t(scale_(0.))); + pts.emplace_back(coord_t(scale_(wti.bb_size(0))), coord_t(scale_(wti.bb_size(1)))); + pts.emplace_back(coord_t(scale_(-0.)), coord_t(scale_(wti.bb_size(1)))); + pts.emplace_back(coord_t(scale_(-0.)), coord_t(scale_(0.))); + Polygon p(std::move(pts)); + ClipperLib::Path clpath = Slic3rMultiPoint_to_ClipperPath(p); + ClipperLib::Polygon pn; + pn.Contour = clpath; + // Efficient conversion to item. + Item item(std::move(pn)); + item.rotation(wti.rotation), + item.translation({ + ClipperLib::cInt(wti.pos(0)/SCALING_FACTOR), + ClipperLib::cInt(wti.pos(1)/SCALING_FACTOR) + }); + ret.emplace_back(nullptr, item); + } + return ret; } @@ -608,7 +630,8 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) { void applyResult( IndexedPackGroup::value_type& group, Coord batch_offset, - ShapeData2D& shapemap) + ShapeData2D& shapemap, + WipeTowerInfo& wti) { for(auto& r : group) { auto idx = r.first; // get the original item index @@ -617,18 +640,25 @@ void applyResult( // Get the model instance from the shapemap using the index ModelInstance *inst_ptr = shapemap[idx].first; - // Get the transformation data from the item object and scale it - // appropriately - auto off = item.translation(); - Radians rot = item.rotation(); + // Get the transformation data from the item object and scale it + // appropriately + auto off = item.translation(); + Radians rot = item.rotation(); - Vec3d foff(off.X*SCALING_FACTOR + batch_offset, - off.Y*SCALING_FACTOR, - inst_ptr->get_offset()(Z)); + Vec3d foff(off.X*SCALING_FACTOR + batch_offset, + off.Y*SCALING_FACTOR, + inst_ptr ? inst_ptr->get_offset()(Z) : 0.); - // write the transformation data into the model instance - inst_ptr->set_rotation(Z, rot); - inst_ptr->set_offset(foff); + if (inst_ptr) { + // write the transformation data into the model instance + inst_ptr->set_rotation(Z, rot); + inst_ptr->set_offset(foff); + } + else { // this is the wipe tower - we will modify the struct with the info + // and leave it up to the called to actually move the wipe tower + wti.pos = Vec2d(foff(0), foff(1)); + wti.rotation = rot; + } } } @@ -714,6 +744,7 @@ BedShapeHint bedShape(const Polyline &bed) { // The final client function to arrange the Model. A progress indicator and // a stop predicate can be also be passed to control the process. bool arrange(Model &model, // The model with the geometries + WipeTowerInfo& wti, // Wipe tower info coord_t min_obj_distance, // Has to be in scaled (clipper) measure const Polyline &bed, // The bed geometry. BedShapeHint bedhint, // Hint about the bed geometry type. @@ -726,7 +757,7 @@ bool arrange(Model &model, // The model with the geometries bool ret = true; // Get the 2D projected shapes with their 3D model instance pointers - auto shapemap = arr::projectModelFromTop(model); + auto shapemap = arr::projectModelFromTop(model, wti); // Copy the references for the shapes only as the arranger expects a // sequence of objects convertible to Item or ClipperPolygon @@ -796,7 +827,7 @@ bool arrange(Model &model, // The model with the geometries if(result.empty() || stopcondition()) return false; if(first_bin_only) { - applyResult(result.front(), 0, shapemap); + applyResult(result.front(), 0, shapemap, wti); } else { const auto STRIDE_PADDING = 1.2; @@ -806,7 +837,7 @@ bool arrange(Model &model, // The model with the geometries Coord batch_offset = 0; for(auto& group : result) { - applyResult(group, batch_offset, shapemap); + applyResult(group, batch_offset, shapemap, wti); // Only the first pack group can be placed onto the print bed. The // other objects which could not fit will be placed next to the @@ -823,10 +854,11 @@ bool arrange(Model &model, // The model with the geometries void find_new_position(const Model &model, ModelInstancePtrs toadd, coord_t min_obj_distance, - const Polyline &bed) + const Polyline &bed, + WipeTowerInfo& wti) { // Get the 2D projected shapes with their 3D model instance pointers - auto shapemap = arr::projectModelFromTop(model); + auto shapemap = arr::projectModelFromTop(model, wti); // Copy the references for the shapes only as the arranger expects a // sequence of objects convertible to Item or ClipperPolygon diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp index d76769081..b61443da0 100644 --- a/src/libslic3r/ModelArrange.hpp +++ b/src/libslic3r/ModelArrange.hpp @@ -40,6 +40,13 @@ struct BedShapeHint { BedShapeHint bedShape(const Polyline& bed); +struct WipeTowerInfo { + bool is_wipe_tower = false; + Vec2d pos; + Vec2d bb_size; + double rotation; +}; + /** * \brief Arranges the model objects on the screen. * @@ -66,7 +73,9 @@ BedShapeHint bedShape(const Polyline& bed); * packed. The unsigned argument is the number of items remaining to pack. * \param stopcondition A predicate returning true if abort is needed. */ -bool arrange(Model &model, coord_t min_obj_distance, +bool arrange(Model &model, + WipeTowerInfo& wipe_tower_info, + coord_t min_obj_distance, const Slic3r::Polyline& bed, BedShapeHint bedhint, bool first_bin_only, @@ -78,7 +87,8 @@ bool arrange(Model &model, coord_t min_obj_distance, void find_new_position(const Model& model, ModelInstancePtrs instances_to_add, coord_t min_obj_distance, - const Slic3r::Polyline& bed); + const Slic3r::Polyline& bed, + WipeTowerInfo& wti); } // arr } // Slic3r diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 7e312cb23..59480de1c 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -716,12 +716,12 @@ int GLVolumeCollection::load_wipe_tower_preview( brim_mesh.translate(-brim_width, -brim_width, 0.f); mesh.merge(brim_mesh); - mesh.rotate(rotation_angle, &origin_of_rotation); // rotates the box according to the config rotation setting - this->volumes.emplace_back(new GLVolume(color)); GLVolume &v = *this->volumes.back(); v.indexed_vertex_array.load_mesh(mesh, use_VBOs); v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0)); + v.set_volume_rotation(Vec3d(0., 0., (M_PI/180.) * rotation_angle)); + // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry(). v.bounding_box = v.indexed_vertex_array.bounding_box(); v.indexed_vertex_array.finalize_geometry(use_VBOs); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 4f7cf14b3..36581369c 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -16,6 +16,7 @@ #include "slic3r/GUI/GLShader.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/PresetBundle.hpp" +#include "slic3r/GUI/Tab.hpp" #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" @@ -1202,6 +1203,7 @@ wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3dEvent); wxDEFINE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event); wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>); wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); @@ -3116,6 +3118,10 @@ void GLCanvas3D::do_rotate() for (const GLVolume* v : m_volumes.volumes) { int object_idx = v->object_idx(); + if (object_idx == 1000) { // the wipe tower + Vec3d offset = v->get_volume_offset(); + post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset(0), offset(1), v->get_volume_rotation()(2)))); + } if ((object_idx < 0) || ((int)m_model->objects.size() <= object_idx)) continue; @@ -3312,6 +3318,38 @@ void GLCanvas3D::update_ui_from_settings() #endif } + + +arr::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const +{ + arr::WipeTowerInfo wti; + for (const GLVolume* vol : m_volumes.volumes) { + if (vol->is_wipe_tower) { + wti.is_wipe_tower = true; + wti.pos = Vec2d(m_config->opt_float("wipe_tower_x"), + m_config->opt_float("wipe_tower_y")); + wti.rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); + const BoundingBoxf3& bb = vol->bounding_box; + wti.bb_size = Vec2d(bb.size()(0), bb.size()(1)); + break; + } + } + return wti; +} + + +void GLCanvas3D::arrange_wipe_tower(const arr::WipeTowerInfo& wti) const +{ + if (wti.is_wipe_tower) { + DynamicPrintConfig cfg; + cfg.opt("wipe_tower_x", true)->value = wti.pos(0); + cfg.opt("wipe_tower_y", true)->value = wti.pos(1); + cfg.opt("wipe_tower_rotation_angle", true)->value = (180./M_PI) * wti.rotation; + wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); + } +} + + Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos) { float z0 = 0.0f; @@ -4315,6 +4353,7 @@ void GLCanvas3D::_render_selection_sidebar_hints() const m_shader.stop_using(); } + void GLCanvas3D::_update_volumes_hover_state() const { for (GLVolume* v : m_volumes.volumes) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 7edef2466..96b958cbf 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -4,6 +4,7 @@ #include #include +#include "libslic3r/ModelArrange.hpp" #include "3DScene.hpp" #include "GLToolbar.hpp" #include "Event.hpp" @@ -116,6 +117,7 @@ wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3dEvent); wxDECLARE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event); wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>); wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); @@ -608,6 +610,9 @@ public: int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; } int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); } + arr::WipeTowerInfo get_wipe_tower_info() const; + void arrange_wipe_tower(const arr::WipeTowerInfo& wti) const; + // Returns the view ray line, in world coordinate, at the given mouse position. Linef3 mouse_ray(const Point& mouse_pos); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index f5946aa56..c65dee4d8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -102,7 +102,7 @@ protected: m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1); } } - virtual bool on_is_activable(const Selection& selection) const { return !selection.is_wipe_tower(); } + virtual bool on_is_activable(const Selection& selection) const { return true; } virtual void on_enable_grabber(unsigned int id) { if ((0 <= id) && (id < 3)) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index dd4e454ae..7c1a7037c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -4,6 +4,7 @@ #include "slic3r/GUI/3DScene.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_ObjectManipulation.hpp" +#include "slic3r/GUI/PresetBundle.hpp" #include #include @@ -264,8 +265,11 @@ void GLGizmosManager::update_data(GLCanvas3D& canvas) const Selection& selection = canvas.get_selection(); - bool enable_move_z = !selection.is_wipe_tower(); - enable_grabber(Move, 2, enable_move_z); + bool is_wipe_tower = selection.is_wipe_tower(); + enable_grabber(Move, 2, !is_wipe_tower); + enable_grabber(Rotate, 0, !is_wipe_tower); + enable_grabber(Rotate, 1, !is_wipe_tower); + bool enable_scale_xyz = selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier(); for (int i = 0; i < 6; ++i) { @@ -290,6 +294,14 @@ void GLGizmosManager::update_data(GLCanvas3D& canvas) set_flattening_data(nullptr); set_sla_support_data(nullptr, selection); } + else if (is_wipe_tower) + { + DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + set_scale(Vec3d::Ones()); + set_rotation(Vec3d(0., 0., (M_PI/180.) * dynamic_cast(config.option("wipe_tower_rotation_angle"))->value)); + set_flattening_data(nullptr); + set_sla_support_data(nullptr, selection); + } else { set_scale(Vec3d::Ones()); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index fa2c166d2..27f5795b2 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1314,6 +1314,7 @@ struct Plater::priv void on_object_select(SimpleEvent&); void on_right_click(Vec2dEvent&); void on_wipetower_moved(Vec3dEvent&); + void on_wipetower_rotated(Vec3dEvent&); void on_update_geometry(Vec3dsEvent<2>&); void on_3dcanvas_mouse_dragging_finished(SimpleEvent&); @@ -1448,6 +1449,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) { if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); }); view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); }); view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this); + view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_ROTATED, &priv::on_wipetower_rotated, this); view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_ROTATED, [this](SimpleEvent&) { update(); }); view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_SCALED, [this](SimpleEvent&) { update(); }); view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event &evt) { this->sidebar->enable_buttons(evt.data); }); @@ -1455,6 +1457,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this); view3D_canvas->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); view3D_canvas->Bind(EVT_GLCANVAS_RESETGIZMOS, [this](SimpleEvent&) { reset_all_gizmos(); }); + // 3DScene/Toolbar: view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); }); @@ -1877,7 +1880,13 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode Polyline bed; bed.points.reserve(bedpoints.size()); for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); - arr::find_new_position(model, new_instances, min_obj_distance, bed); + arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info(); + + arr::find_new_position(model, new_instances, min_obj_distance, bed, wti); + + // it remains to move the wipe tower: + view3D->get_canvas3d()->arrange_wipe_tower(wti); + #endif /* AUTOPLACEMENT_ON_LOAD */ if (scaled_down) { @@ -2134,6 +2143,8 @@ void Plater::priv::arrange() statusfn(0, arrangestr); + arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info(); + try { arr::BedShapeHint hint; @@ -2141,6 +2152,7 @@ void Plater::priv::arrange() hint.type = arr::BedShapeType::WHO_KNOWS; arr::arrange(model, + wti, min_obj_distance, bed, hint, @@ -2152,6 +2164,9 @@ void Plater::priv::arrange() "Some geometries may be invalid.")); } + // it remains to move the wipe tower: + view3D->get_canvas3d()->arrange_wipe_tower(wti); + statusfn(0, L("Arranging done.")); statusbar()->set_range(prev_range); statusbar()->set_cancel_callback(); // remove cancel button @@ -2254,7 +2269,8 @@ void Plater::priv::sla_optimize_rotation() { oi->set_rotation(rt); } - arr::find_new_position(model, o->instances, coord_t(mindist/SCALING_FACTOR), bed); + arr::WipeTowerInfo wti; // useless in SLA context + arr::find_new_position(model, o->instances, coord_t(mindist/SCALING_FACTOR), bed, wti); // Correct the z offset of the object which was corrupted be the rotation o->ensure_on_bed(); @@ -2862,6 +2878,15 @@ void Plater::priv::on_wipetower_moved(Vec3dEvent &evt) wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); } +void Plater::priv::on_wipetower_rotated(Vec3dEvent& evt) +{ + DynamicPrintConfig cfg; + cfg.opt("wipe_tower_x", true)->value = evt.data(0); + cfg.opt("wipe_tower_y", true)->value = evt.data(1); + cfg.opt("wipe_tower_rotation_angle", true)->value = Geometry::rad2deg(evt.data(2)); + wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); +} + void Plater::priv::on_update_geometry(Vec3dsEvent<2>&) { // TODO diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index eaa00bf6f..ee47446cf 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -499,100 +499,111 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ // Only relative rotation values are allowed in the world coordinate system. assert(!transformation_type.world() || transformation_type.relative()); - int rot_axis_max = 0; - if (rotation.isApprox(Vec3d::Zero())) - { - for (unsigned int i : m_list) + if (!is_wipe_tower()) { + int rot_axis_max = 0; + if (rotation.isApprox(Vec3d::Zero())) { - GLVolume &volume = *(*m_volumes)[i]; - if (m_mode == Instance) - { - volume.set_instance_rotation(m_cache.volumes_data[i].get_instance_rotation()); - volume.set_instance_offset(m_cache.volumes_data[i].get_instance_position()); - } - else if (m_mode == Volume) - { - volume.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation()); - volume.set_volume_offset(m_cache.volumes_data[i].get_volume_position()); - } - } - } - else - { - //FIXME this does not work for absolute rotations (transformation_type.absolute() is true) - rotation.cwiseAbs().maxCoeff(&rot_axis_max); - - // For generic rotation, we want to rotate the first volume in selection, and then to synchronize the other volumes with it. - std::vector object_instance_first(m_model->objects.size(), -1); - auto rotate_instance = [this, &rotation, &object_instance_first, rot_axis_max, transformation_type](GLVolume &volume, int i) { - int first_volume_idx = object_instance_first[volume.object_idx()]; - if (rot_axis_max != 2 && first_volume_idx != -1) { - // Generic rotation, but no rotation around the Z axis. - // Always do a local rotation (do not consider the selection to be a rigid body). - assert(is_approx(rotation.z(), 0.0)); - const GLVolume &first_volume = *(*m_volumes)[first_volume_idx]; - const Vec3d &rotation = first_volume.get_instance_rotation(); - double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation()); - volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); - } - else { - // extracts rotations from the composed transformation - Vec3d new_rotation = transformation_type.world() ? - Geometry::extract_euler_angles(Geometry::assemble_transform(Vec3d::Zero(), rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix()) : - transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation(); - if (rot_axis_max == 2 && transformation_type.joint()) { - // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. - Vec3d offset = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0.0, 0.0, new_rotation(2) - m_cache.volumes_data[i].get_instance_rotation()(2))) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); - volume.set_instance_offset(m_cache.dragging_center + offset); - } - volume.set_instance_rotation(new_rotation); - object_instance_first[volume.object_idx()] = i; - } - }; - - for (unsigned int i : m_list) - { - GLVolume &volume = *(*m_volumes)[i]; - if (is_single_full_instance()) - rotate_instance(volume, i); - else if (is_single_volume() || is_single_modifier()) - { - if (transformation_type.independent()) - volume.set_volume_rotation(volume.get_volume_rotation() + rotation); - else - { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); - volume.set_volume_rotation(new_rotation); - } - } - else + for (unsigned int i : m_list) { + GLVolume &volume = *(*m_volumes)[i]; if (m_mode == Instance) - rotate_instance(volume, i); + { + volume.set_instance_rotation(m_cache.volumes_data[i].get_instance_rotation()); + volume.set_instance_offset(m_cache.volumes_data[i].get_instance_position()); + } else if (m_mode == Volume) { - // extracts rotations from the composed transformation - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); - if (transformation_type.joint()) - { - Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center; - Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot); - volume.set_volume_offset(local_pivot + offset); - } - volume.set_volume_rotation(new_rotation); + volume.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation()); + volume.set_volume_offset(m_cache.volumes_data[i].get_volume_position()); } } } - } + else { // this is not the wipe tower + //FIXME this does not work for absolute rotations (transformation_type.absolute() is true) + rotation.cwiseAbs().maxCoeff(&rot_axis_max); -#if !DISABLE_INSTANCES_SYNCH - if (m_mode == Instance) - synchronize_unselected_instances((rot_axis_max == 2) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL); - else if (m_mode == Volume) - synchronize_unselected_volumes(); -#endif // !DISABLE_INSTANCES_SYNCH + // For generic rotation, we want to rotate the first volume in selection, and then to synchronize the other volumes with it. + std::vector object_instance_first(m_model->objects.size(), -1); + auto rotate_instance = [this, &rotation, &object_instance_first, rot_axis_max, transformation_type](GLVolume &volume, int i) { + int first_volume_idx = object_instance_first[volume.object_idx()]; + if (rot_axis_max != 2 && first_volume_idx != -1) { + // Generic rotation, but no rotation around the Z axis. + // Always do a local rotation (do not consider the selection to be a rigid body). + assert(is_approx(rotation.z(), 0.0)); + const GLVolume &first_volume = *(*m_volumes)[first_volume_idx]; + const Vec3d &rotation = first_volume.get_instance_rotation(); + double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation()); + volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); + } + else { + // extracts rotations from the composed transformation + Vec3d new_rotation = transformation_type.world() ? + Geometry::extract_euler_angles(Geometry::assemble_transform(Vec3d::Zero(), rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix()) : + transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation(); + if (rot_axis_max == 2 && transformation_type.joint()) { + // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. + Vec3d offset = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0.0, 0.0, new_rotation(2) - m_cache.volumes_data[i].get_instance_rotation()(2))) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); + volume.set_instance_offset(m_cache.dragging_center + offset); + } + volume.set_instance_rotation(new_rotation); + object_instance_first[volume.object_idx()] = i; + } + }; + + for (unsigned int i : m_list) + { + GLVolume &volume = *(*m_volumes)[i]; + if (is_single_full_instance()) + rotate_instance(volume, i); + else if (is_single_volume() || is_single_modifier()) + { + if (transformation_type.independent()) + volume.set_volume_rotation(volume.get_volume_rotation() + rotation); + else + { + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); + volume.set_volume_rotation(new_rotation); + } + } + else + { + if (m_mode == Instance) + rotate_instance(volume, i); + else if (m_mode == Volume) + { + // extracts rotations from the composed transformation + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); + if (transformation_type.joint()) + { + Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center; + Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot); + volume.set_volume_offset(local_pivot + offset); + } + volume.set_volume_rotation(new_rotation); + } + } + } + } + + #if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) + synchronize_unselected_instances((rot_axis_max == 2) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL); + else if (m_mode == Volume) + synchronize_unselected_volumes(); + #endif // !DISABLE_INSTANCES_SYNCH + } + else { // it's the wipe tower that's selected and being rotated + GLVolume& volume = *((*m_volumes)[*m_list.begin()]); // the wipe tower is always alone in the selection + + // make sure the wipe tower rotates around its center, not origin + // we can assume that only Z rotation changes + Vec3d center_local = volume.transformed_bounding_box().center() - volume.get_volume_offset(); + Vec3d center_local_new = Eigen::AngleAxisd(rotation(2)-volume.get_volume_rotation()(2), Vec3d(0, 0, 1)) * center_local; + volume.set_volume_rotation(rotation); + volume.set_volume_offset(volume.get_volume_offset() + center_local - center_local_new); + } this->set_bounding_boxes_dirty(); }