diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index d8770cbfe..58324893d 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -1188,16 +1188,16 @@ Vec3d extract_euler_angles(const Eigen::Matrix& Vec3d angles2 = Vec3d::Zero(); if (is_approx(std::abs(rotation_matrix(2, 0)), 1.0)) { - angles1(0) = 0.0; - if (rotation_matrix(2, 0) > 0.0) // == +1.0 + angles1(2) = 0.0; + if (rotation_matrix(2, 0) < 0.0) // == -1.0 { angles1(1) = 0.5 * (double)PI; - angles1(2) = angles1(0) + ::atan2(rotation_matrix(0, 1), rotation_matrix(0, 2)); + angles1(0) = angles1(2) + ::atan2(rotation_matrix(0, 1), rotation_matrix(0, 2)); } - else // == -1.0 + else // == 1.0 { - angles1(1) = 0.5 * (double)PI; - angles1(2) = -angles1(0) - ::atan2(rotation_matrix(0, 1), rotation_matrix(0, 2)); + angles1(1) = - 0.5 * (double)PI; + angles1(0) = - angles1(2) + ::atan2(- rotation_matrix(0, 1), - rotation_matrix(0, 2)); } angles2 = angles1; } diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index deefaa789..b43d59143 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -237,7 +237,7 @@ public: void set_rotation(const Vec3d& rotation); void set_rotation(Axis axis, double rotation); - Vec3d get_scaling_factor() const { return m_scaling_factor; } + const Vec3d& get_scaling_factor() const { return m_scaling_factor; } double get_scaling_factor(Axis axis) const { return m_scaling_factor(axis); } void set_scaling_factor(const Vec3d& scaling_factor); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index afc5d0741..0ae238510 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -473,7 +473,7 @@ public: void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); } - Vec3d get_scaling_factor() const { return m_transformation.get_scaling_factor(); } + const Vec3d& get_scaling_factor() const { return m_transformation.get_scaling_factor(); } double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); } void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index cfe1a1b56..7366516d1 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1770,7 +1770,7 @@ void GLCanvas3D::Selection::translate(const Vec3d& displacement, bool local) #if !DISABLE_INSTANCES_SYNCH if (m_mode == Instance) - _synchronize_unselected_instances(); + _synchronize_unselected_instances(SYNC_ROTATION_NONE); else if (m_mode == Volume) _synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH @@ -1778,64 +1778,100 @@ void GLCanvas3D::Selection::translate(const Vec3d& displacement, bool local) m_bounding_box_dirty = true; } +static Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) +{ + return + // From the current coordinate system to world. + Eigen::AngleAxisd(rot_xyz_to(2), Vec3d::UnitZ()) * Eigen::AngleAxisd(rot_xyz_to(1), Vec3d::UnitY()) * Eigen::AngleAxisd(rot_xyz_to(0), Vec3d::UnitX()) * + // From world to the initial coordinate system. + Eigen::AngleAxisd(-rot_xyz_from(0), Vec3d::UnitX()) * Eigen::AngleAxisd(-rot_xyz_from(1), Vec3d::UnitY()) * Eigen::AngleAxisd(-rot_xyz_from(2), Vec3d::UnitZ()); +} + +// This should only be called if it is known, that the two rotations only differ in rotation around the Z axis. +static double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) +{ + Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); + Vec3d axis = angle_axis.axis(); + double angle = angle_axis.angle(); +#ifdef _DEBUG + if (std::abs(angle) > 1e-8) { + assert(std::abs(axis.x()) < 1e-8); + assert(std::abs(axis.y()) < 1e-8); + } +#endif /* _DEBUG */ + return (axis.z() < 0) ? -angle : angle; +} + void GLCanvas3D::Selection::rotate(const Vec3d& rotation, bool local) { if (!m_valid) return; + int rot_axis_max; + 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, local](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(rotation.z() == 0); + const GLVolume &first_volume = *(*m_volumes)[first_volume_idx]; + const Vec3d &rotation = first_volume.get_instance_rotation(); + double z_diff = 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 + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_instance_rotation_matrix()); + if (rot_axis_max == 2 && !local) + // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. + volume.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); + 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()) - { - if (local) - (*m_volumes)[i]->set_instance_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_instance_rotation_matrix()); - (*m_volumes)[i]->set_instance_rotation(new_rotation); - } - } + rotate_instance(volume, i); else if (is_single_volume() || is_single_modifier()) { if (local) - (*m_volumes)[i]->set_volume_rotation(rotation); + volume.set_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()); - (*m_volumes)[i]->set_volume_rotation(new_rotation); + volume.set_volume_rotation(new_rotation); } } else { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); if (m_mode == Instance) - { - // extracts rotations from the composed transformation - Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_instance_rotation_matrix()); - if (!local) - (*m_volumes)[i]->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_rotation(new_rotation); - } + 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 (!local) { 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_rotation(new_rotation); + volume.set_volume_rotation(new_rotation); } } } #if !DISABLE_INSTANCES_SYNCH if (m_mode == Instance) - _synchronize_unselected_instances(); + _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 @@ -1878,7 +1914,7 @@ void GLCanvas3D::Selection::flattening_rotate(const Vec3d& normal) // we want to synchronize z-rotation as well, otherwise the flattening behaves funny // when applied on one of several identical instances if (m_mode == Instance) - _synchronize_unselected_instances(true); + _synchronize_unselected_instances(SYNC_ROTATION_FULL); #endif // !DISABLE_INSTANCES_SYNCH m_bounding_box_dirty = true; @@ -1925,7 +1961,7 @@ void GLCanvas3D::Selection::scale(const Vec3d& scale, bool local) #if !DISABLE_INSTANCES_SYNCH if (m_mode == Instance) - _synchronize_unselected_instances(); + _synchronize_unselected_instances(SYNC_ROTATION_NONE); else if (m_mode == Volume) _synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH @@ -1952,7 +1988,7 @@ void GLCanvas3D::Selection::mirror(Axis axis) #if !DISABLE_INSTANCES_SYNCH if (m_mode == Instance) - _synchronize_unselected_instances(); + _synchronize_unselected_instances(SYNC_ROTATION_NONE); else if (m_mode == Volume) _synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH @@ -2750,7 +2786,43 @@ void GLCanvas3D::Selection::_render_sidebar_size_hint(Axis axis, double length) { } -void GLCanvas3D::Selection::_synchronize_unselected_instances(bool including_z) +#ifdef _DEBUG +static bool is_rotation_xy_synchronized(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) +{ + Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); + Vec3d axis = angle_axis.axis(); + double angle = angle_axis.angle(); + if (std::abs(angle) < 1e-8) + return true; + assert(std::abs(axis.x()) < 1e-8); + assert(std::abs(axis.y()) < 1e-8); + assert(std::abs(std::abs(axis.z()) - 1.) < 1e-8); + return std::abs(axis.x()) < 1e-8 && std::abs(axis.y()) < 1e-8 && std::abs(std::abs(axis.z()) - 1.) < 1e-8; +} +static void verify_instances_rotation_synchronized(const Model &model, const GLVolumePtrs &volumes) +{ + for (size_t idx_object = 0; idx_object < model.objects.size(); ++ idx_object) { + int idx_volume_first = -1; + for (int i = 0; i < (int)volumes.size(); ++ i) { + if (volumes[i]->object_idx() == idx_object) { + idx_volume_first = i; + break; + } + } + assert(idx_volume_first != -1); // object without instances? + if (idx_volume_first == -1) + continue; + const Vec3d &rotation0 = volumes[idx_volume_first]->get_instance_rotation(); + for (int i = idx_volume_first + 1; i < (int)volumes.size(); ++ i) + if (volumes[i]->object_idx() == idx_object) { + const Vec3d &rotation = volumes[i]->get_instance_rotation(); + assert(is_rotation_xy_synchronized(rotation, rotation0)); + } + } +} +#endif /* _DEBUG */ + +void GLCanvas3D::Selection::_synchronize_unselected_instances(SyncRotationType sync_rotation_type) { std::set done; // prevent processing volumes twice done.insert(m_list.begin(), m_list.end()); @@ -2766,7 +2838,7 @@ void GLCanvas3D::Selection::_synchronize_unselected_instances(bool including_z) continue; int instance_idx = volume->instance_idx(); - const Vec3d& rotation = volume->get_instance_rotation(); + const Vec3d& rotation = volume->get_instance_rotation(); const Vec3d& scaling_factor = volume->get_instance_scaling_factor(); const Vec3d& mirror = volume->get_instance_mirror(); @@ -2783,26 +2855,34 @@ void GLCanvas3D::Selection::_synchronize_unselected_instances(bool including_z) if ((v->object_idx() != object_idx) || (v->instance_idx() == instance_idx)) continue; - auto is_approx = [](double value, double test_value) -> bool { return std::abs(value - test_value) < EPSILON; }; - - double z; - if (including_z) + assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); + switch (sync_rotation_type) { + case SYNC_ROTATION_NONE: + // z only rotation -> keep instance z + // The X,Y rotations should be synchronized from start to end of the rotation. + assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation())); + break; + case SYNC_ROTATION_FULL: // rotation comes from place on face -> force given z - z = rotation(2); - else if (is_approx(rotation(0), m_cache.volumes_data[j].get_instance_rotation()(0)) && is_approx(rotation(1), m_cache.volumes_data[j].get_instance_rotation()(1))) - // z only rotation -> keep instance z - z = v->get_instance_rotation()(2); - else - // generic rotation -> update instance z - z = m_cache.volumes_data[j].get_instance_rotation()(2) + rotation(2); + v->set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2))); + break; + case SYNC_ROTATION_GENERAL: + // generic rotation -> update instance z with the delta of the rotation. + double z_diff = rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); + v->set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); + break; + } - v->set_instance_rotation(Vec3d(rotation(0), rotation(1), z)); v->set_instance_scaling_factor(scaling_factor); v->set_instance_mirror(mirror); done.insert(j); } } + +#ifdef _DEBUG + verify_instances_rotation_synchronized(*m_model, *m_volumes); +#endif /* _DEBUG */ } void GLCanvas3D::Selection::_synchronize_unselected_volumes() @@ -4588,7 +4668,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re } if (printer_technology == ptSLA) { const SLAPrint *sla_print = this->sla_print(); - #ifdef _DEBUG + #ifdef _DEBUG // Verify that the SLAPrint object is synchronized with m_model. check_model_ids_equal(*m_model, sla_print->model()); #endif /* _DEBUG */ diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 578c7fe08..8bdbf89b0 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -670,7 +670,15 @@ public: void _render_sidebar_rotation_hint(Axis axis) const; void _render_sidebar_scale_hint(Axis axis) const; void _render_sidebar_size_hint(Axis axis, double length) const; - void _synchronize_unselected_instances(bool including_z = false); + enum SyncRotationType { + // Do not synchronize rotation. Either not rotating at all, or rotating by world Z axis. + SYNC_ROTATION_NONE = 0, + // Synchronize fully. Used from "place on bed" feature. + SYNC_ROTATION_FULL = 1, + // Synchronize after rotation by an axis not parallel with Z. + SYNC_ROTATION_GENERAL = 2, + }; + void _synchronize_unselected_instances(SyncRotationType sync_rotation_type); void _synchronize_unselected_volumes(); void _ensure_on_bed(); };