diff --git a/resources/images/toolbar_assembly.svg b/resources/images/toolbar_assembly.svg
index 4fba90b72c..1c2a025f5e 100644
--- a/resources/images/toolbar_assembly.svg
+++ b/resources/images/toolbar_assembly.svg
@@ -1,7 +1,7 @@
diff --git a/resources/images/toolbar_assembly_dark.svg b/resources/images/toolbar_assembly_dark.svg
index 302b4d745c..f514dcb5cc 100644
--- a/resources/images/toolbar_assembly_dark.svg
+++ b/resources/images/toolbar_assembly_dark.svg
@@ -1,7 +1,7 @@
diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp
index 49e50a671d..3e67d4e1ba 100644
--- a/src/libslic3r/Geometry.cpp
+++ b/src/libslic3r/Geometry.cpp
@@ -717,53 +717,6 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation
return out;
}
-TransformationSVD::TransformationSVD(const Transform3d& trafo)
-{
- const auto &m0 = trafo.matrix().block<3, 3>(0, 0);
- mirror = m0.determinant() < 0.0;
-
- Matrix3d m;
- if (mirror)
- m = m0 * Eigen::DiagonalMatrix(-1.0, 1.0, 1.0);
- else
- m = m0;
- const Eigen::JacobiSVD svd(m, Eigen::ComputeFullU | Eigen::ComputeFullV);
- u = svd.matrixU();
- v = svd.matrixV();
- s = svd.singularValues().asDiagonal();
-
- scale = !s.isApprox(Matrix3d::Identity());
- anisotropic_scale = ! is_approx(s(0, 0), s(1, 1)) || ! is_approx(s(1, 1), s(2, 2));
- rotation = !v.isApprox(u);
-
- if (anisotropic_scale) {
- rotation_90_degrees = true;
- for (int i = 0; i < 3; ++i) {
- const Vec3d row = v.row(i).cwiseAbs();
- const size_t num_zeros = is_approx(row[0], 0.) + is_approx(row[1], 0.) + is_approx(row[2], 0.);
- const size_t num_ones = is_approx(row[0], 1.) + is_approx(row[1], 1.) + is_approx(row[2], 1.);
- if (num_zeros != 2 || num_ones != 1) {
- rotation_90_degrees = false;
- break;
- }
- }
- // Detect skew by brute force: check if the axes are still orthogonal after transformation
- const Matrix3d trafo_linear = trafo.linear();
- const std::array axes = { Vec3d::UnitX(), Vec3d::UnitY(), Vec3d::UnitZ() };
- std::array transformed_axes;
- for (int i = 0; i < 3; ++i) {
- transformed_axes[i] = trafo_linear * axes[i];
- }
- skew = std::abs(transformed_axes[0].dot(transformed_axes[1])) > EPSILON ||
- std::abs(transformed_axes[1].dot(transformed_axes[2])) > EPSILON ||
- std::abs(transformed_axes[2].dot(transformed_axes[0])) > EPSILON;
-
- // This following old code does not work under all conditions. The v matrix can become non diagonal (see SPE-1492)
-// skew = ! rotation_90_degrees;
- } else
- skew = false;
-}
-
// For parsing a transformation matrix from 3MF / AMF.
Transform3d transform3d_from_string(const std::string& transform_str)
{
@@ -812,4 +765,78 @@ double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to)
return (axis.z() < 0) ? -angle : angle;
}
+TransformationSVD::TransformationSVD(const Transform3d& trafo)
+{
+ const auto &m0 = trafo.matrix().block<3, 3>(0, 0);
+ mirror = m0.determinant() < 0.0;
+
+ Matrix3d m;
+ if (mirror)
+ m = m0 * Eigen::DiagonalMatrix(-1.0, 1.0, 1.0);
+ else
+ m = m0;
+ const Eigen::JacobiSVD svd(m, Eigen::ComputeFullU | Eigen::ComputeFullV);
+ u = svd.matrixU();
+ v = svd.matrixV();
+ s = svd.singularValues().asDiagonal();
+
+ scale = !s.isApprox(Matrix3d::Identity());
+ anisotropic_scale = ! is_approx(s(0, 0), s(1, 1)) || ! is_approx(s(1, 1), s(2, 2));
+ rotation = !v.isApprox(u);
+
+ if (anisotropic_scale) {
+ rotation_90_degrees = true;
+ for (int i = 0; i < 3; ++i) {
+ const Vec3d row = v.row(i).cwiseAbs();
+ const size_t num_zeros = is_approx(row[0], 0.) + is_approx(row[1], 0.) + is_approx(row[2], 0.);
+ const size_t num_ones = is_approx(row[0], 1.) + is_approx(row[1], 1.) + is_approx(row[2], 1.);
+ if (num_zeros != 2 || num_ones != 1) {
+ rotation_90_degrees = false;
+ break;
+ }
+ }
+ // Detect skew by brute force: check if the axes are still orthogonal after transformation
+ const Matrix3d trafo_linear = trafo.linear();
+ const std::array axes = { Vec3d::UnitX(), Vec3d::UnitY(), Vec3d::UnitZ() };
+ std::array transformed_axes;
+ for (int i = 0; i < 3; ++i) {
+ transformed_axes[i] = trafo_linear * axes[i];
+ }
+ skew = std::abs(transformed_axes[0].dot(transformed_axes[1])) > EPSILON ||
+ std::abs(transformed_axes[1].dot(transformed_axes[2])) > EPSILON ||
+ std::abs(transformed_axes[2].dot(transformed_axes[0])) > EPSILON;
+
+ // This following old code does not work under all conditions. The v matrix can become non diagonal (see SPE-1492)
+// skew = ! rotation_90_degrees;
+ } else
+ skew = false;
+}
+
+ Transformation mat_around_a_point_rotate(const Transformation &InMat, const Vec3d &pt, const Vec3d &axis, float rotate_theta_radian)
+{
+ auto xyz = InMat.get_offset();
+ Transformation left;
+ left.set_offset(-xyz); // at world origin
+ auto curMat = left * InMat;
+
+ auto qua = Eigen::Quaterniond(Eigen::AngleAxisd(rotate_theta_radian, axis));
+ qua.normalize();
+ Transform3d cur_matrix;
+ Transformation rotateMat4;
+ rotateMat4.set_matrix(cur_matrix.fromPositionOrientationScale(Vec3d(0., 0., 0.), qua, Vec3d(1., 1., 1.)));
+
+ curMat = rotateMat4 * curMat; // along_fix_axis
+ // rotate mat4 along fix pt
+ Transformation temp_world;
+ auto qua_world = Eigen::Quaterniond(Eigen::AngleAxisd(0, axis));
+ qua_world.normalize();
+ Transform3d cur_matrix_world;
+ temp_world.set_matrix(cur_matrix_world.fromPositionOrientationScale(pt, qua_world, Vec3d(1., 1., 1.)));
+ auto temp_xyz = temp_world.get_matrix().inverse() * xyz;
+ auto new_pos = temp_world.get_matrix() * (rotateMat4.get_matrix() * temp_xyz);
+ curMat.set_offset(new_pos);
+
+ return curMat;
+}
+
}} // namespace Slic3r::Geometry
diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp
index 2b027a231a..8183dcb7e8 100644
--- a/src/libslic3r/Geometry.hpp
+++ b/src/libslic3r/Geometry.hpp
@@ -10,7 +10,7 @@
// Serialization through the Cereal library
#include
-namespace Slic3r {
+namespace Slic3r {
namespace ClipperLib {
class PolyNode;
@@ -544,6 +544,7 @@ inline bool is_rotation_ninety_degrees(const Vec3d &rotation)
return is_rotation_ninety_degrees(rotation.x()) && is_rotation_ninety_degrees(rotation.y()) && is_rotation_ninety_degrees(rotation.z());
}
+Transformation mat_around_a_point_rotate(const Transformation& innMat, const Vec3d &pt, const Vec3d &axis, float rotate_theta_radian);
} } // namespace Slicer::Geometry
#endif
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 0c54d835ad..10d3f37648 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -145,6 +145,8 @@ set(SLIC3R_GUI_SOURCES
#GUI/Gizmos/GLGizmoFaceDetector.hpp
GUI/Gizmos/GLGizmoMeasure.cpp
GUI/Gizmos/GLGizmoMeasure.hpp
+ GUI/Gizmos/GLGizmoAssembly.cpp
+ GUI/Gizmos/GLGizmoAssembly.hpp
GUI/Gizmos/GLGizmoSeam.cpp
GUI/Gizmos/GLGizmoSeam.hpp
#GUI/Gizmos/GLGizmoText.cpp
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp b/src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp
new file mode 100644
index 0000000000..c20ec975c7
--- /dev/null
+++ b/src/slic3r/GUI/Gizmos/GLGizmoAssembly.cpp
@@ -0,0 +1,167 @@
+#include "GLGizmoAssembly.hpp"
+#include "slic3r/GUI/GLCanvas3D.hpp"
+#include "slic3r/GUI/GUI_App.hpp"
+#include "slic3r/GUI/Plater.hpp"
+#include "slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp"
+#include "slic3r/Utils/UndoRedo.hpp"
+
+#include "libslic3r/PresetBundle.hpp"
+#include "libslic3r/MeasureUtils.hpp"
+
+#include
+
+#include
+
+#include
+
+#include
+#include
+#include
+
+namespace Slic3r {
+namespace GUI {
+
+GLGizmoAssembly::GLGizmoAssembly(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) :
+ GLGizmoMeasure(parent, icon_filename, sprite_id)
+{
+ m_measure_mode = EMeasureMode::ONLY_ASSEMBLY;
+}
+
+std::string GLGizmoAssembly::on_get_name() const
+{
+ if (!on_is_activable() && m_state == EState::Off) {
+ if (wxGetApp().plater()->canvas3D()->get_canvas_type() == GLCanvas3D::ECanvasType::CanvasAssembleView) {
+ return _u8L("Assemble") + ":\n" + _u8L("Please confirm explosion ratio = 1 and select at least two volumes.");
+ }
+ else {
+ return _u8L("Assemble") + ":\n" + _u8L("Please select at least two volumes.");
+ }
+ } else {
+ return _u8L("Assemble");
+ }
+}
+
+bool GLGizmoAssembly::on_is_activable() const
+{
+ const Selection& selection = m_parent.get_selection();
+ if (selection.is_wipe_tower()) {
+ return false;
+ }
+ const int selection_volumes_count = 2;
+ if (wxGetApp().plater()->canvas3D()->get_canvas_type() == GLCanvas3D::ECanvasType::CanvasAssembleView) {
+ if (abs(m_parent.get_explosion_ratio() - 1.0f) < 1e-2 && selection.volumes_count() >= selection_volumes_count) {
+ return true;
+ }
+ return false;
+ } else {
+ return selection.volumes_count() >= selection_volumes_count;
+ }
+}
+
+void GLGizmoAssembly::on_render_input_window(float x, float y, float bottom_limit)
+{
+ static std::optional last_feature;
+ static EMode last_mode = EMode::FeatureSelection;
+ static SelectedFeatures last_selected_features;
+
+ static float last_y = 0.0f;
+ static float last_h = 0.0f;
+
+ if (m_editing_distance)
+ return;
+ m_current_active_imgui_id = ImGui::GetActiveID();
+ // adjust window position to avoid overlap the view toolbar
+ const float win_h = ImGui::GetWindowHeight();
+ y = std::min(y, bottom_limit - win_h);
+ GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f);
+ if (last_h != win_h || last_y != y) {
+ // ask canvas for another frame to render the window in the correct position
+ m_imgui->set_requires_extra_frame();
+ if (last_h != win_h)
+ last_h = win_h;
+ if (last_y != y)
+ last_y = y;
+ }
+ // Orca
+ ImGuiWrapper::push_toolbar_style(m_parent.get_scale());
+ GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
+ init_render_input_window();
+
+ float moving_size = m_imgui->calc_text_size(_L("(Moving)")).x;
+ float combox_content_size = m_imgui->calc_text_size(_L("Point and point assembly")).x*1.1 + ImGui::GetStyle().FramePadding.x * 18.0f;
+ float caption_size = moving_size + 2 * m_space_size;
+ if (render_assembly_mode_combo(caption_size + 0.5 * m_space_size, combox_content_size)) {
+ ;
+ }
+ show_selection_ui();
+ show_face_face_assembly_common();
+ ImGui::Separator();
+ show_face_face_assembly_senior();
+ show_distance_xyz_ui();
+ render_input_window_warning(m_same_model_object);
+ ImGui::Separator();
+
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f));
+ float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y;
+ float caption_max = 0.f;
+ float total_text_max = 0.f;
+ for (const auto &t : std::array{"point_selection", "reset", "unselect"}) {
+ caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x);
+ total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x);
+ }
+ show_tooltip_information(caption_max, x, get_cur_y);
+
+ float f_scale =m_parent.get_gizmos_manager().get_layout_scale();
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
+
+ ImGui::PopStyleVar(2);
+
+ if (last_feature != m_curr_feature || last_mode != m_mode || last_selected_features != m_selected_features) {
+ // the dialog may have changed its size, ask for an extra frame to render it properly
+ last_feature = m_curr_feature;
+ last_mode = m_mode;
+ last_selected_features = m_selected_features;
+ m_imgui->set_requires_extra_frame();
+ }
+ m_last_active_item_imgui = m_current_active_imgui_id;
+ GizmoImguiEnd();
+ // Orca
+ ImGuiWrapper::pop_toolbar_style();
+}
+
+void GLGizmoAssembly::render_input_window_warning(bool same_model_object)
+{
+ if (wxGetApp().plater()->canvas3D()->get_canvas_type() == GLCanvas3D::ECanvasType::CanvasView3D) {
+ if (m_hit_different_volumes.size() == 2) {
+ if (same_model_object == false) {
+ m_imgui->warning_text(_L("Warning") + ": " +
+ _L("It is recommended to assemble the objects first,\nbecause the objects is restriced to bed \nand only parts can be lifted."));
+ }
+ }
+ }
+}
+
+bool GLGizmoAssembly::render_assembly_mode_combo(double label_width, float item_width)
+{
+ ImGui::AlignTextToFramePadding();
+ int selection_idx = int(m_assembly_mode);
+ std::vector modes = {_u8L("Face and face assembly"), _u8L("Point and point assembly")};
+ bool is_changed = false;
+
+ ImGuiWrapper::push_combo_style(m_parent.get_scale());
+ if (render_combo(_u8L("Mode"), modes, selection_idx, label_width, item_width)) {
+ is_changed = true;
+ switch_to_mode((AssemblyMode) selection_idx);
+ }
+ ImGuiWrapper::pop_combo_style();
+ return is_changed;
+}
+
+void GLGizmoAssembly::switch_to_mode(AssemblyMode new_mode)
+{
+ m_assembly_mode = new_mode;
+ reset_all_feature();
+}
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAssembly.hpp b/src/slic3r/GUI/Gizmos/GLGizmoAssembly.hpp
new file mode 100644
index 0000000000..7046337b60
--- /dev/null
+++ b/src/slic3r/GUI/Gizmos/GLGizmoAssembly.hpp
@@ -0,0 +1,43 @@
+#ifndef slic3r_GLGizmoAssembly_hpp_
+#define slic3r_GLGizmoAssembly_hpp_
+
+#include "GLGizmoMeasure.hpp"
+
+namespace Slic3r {
+
+namespace GUI {
+class GLGizmoAssembly : public GLGizmoMeasure
+{
+
+public:
+ GLGizmoAssembly(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
+ ///
+ /// Apply rotation on select plane
+ ///
+ /// Keep information about mouse click
+ /// Return True when use the information otherwise False.
+ //bool on_mouse(const wxMouseEvent &mouse_event) override;
+ //void data_changed(bool is_serializing) override;
+ //bool gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_position, bool shift_down, bool alt_down, bool control_down) override;
+
+ bool wants_enter_leave_snapshots() const override { return true; }
+ std::string get_gizmo_entering_text() const override { return _u8L("Entering Assembly gizmo"); }
+ std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Assembly gizmo"); }
+protected:
+ //bool on_init() override;
+ std::string on_get_name() const override;
+ bool on_is_activable() const override;
+ //void on_render() override;
+ //void on_set_state() override;
+ virtual void on_render_input_window(float x, float y, float bottom_limit) override;
+
+ void render_input_window_warning(bool same_model_object) override;
+ bool render_assembly_mode_combo(double label_width, float item_width);
+
+ void switch_to_mode(AssemblyMode new_mode);
+};
+
+} // namespace GUI
+} // namespace Slic3r
+
+#endif // slic3r_GLGizmoAssembly_hpp_
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp
index 0a219a5cc3..97875ee606 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp
@@ -194,6 +194,39 @@ void GLGizmoBase::Grabber::render(float size, const ColorRGBA& render_color)
}
}
+bool GLGizmoBase::render_combo(const std::string &label, const std::vector &lines, int &selection_idx, float label_width, float item_width)
+{
+ ImGuiWrapper::push_combo_style(m_parent.get_scale());
+ ImGui::AlignTextToFramePadding();
+ m_imgui->text(label);
+ ImGui::SameLine(label_width);
+ ImGui::PushItemWidth(item_width);
+
+ size_t selection_out = selection_idx;
+
+ const char *selected_str = (selection_idx >= 0 && selection_idx < int(lines.size())) ? lines[selection_idx].c_str() : "";
+ if (ImGui::BBLBeginCombo(("##" + label).c_str(), selected_str, 0)) {
+ for (size_t line_idx = 0; line_idx < lines.size(); ++line_idx) {
+ ImGui::PushID(int(line_idx));
+ if (ImGui::Selectable("", line_idx == selection_idx)) selection_out = line_idx;
+
+ ImGui::SameLine();
+ ImGui::Text("%s", lines[line_idx].c_str());
+ ImGui::PopID();
+ }
+
+ ImGui::EndCombo();
+ }
+
+ bool is_changed = selection_idx != selection_out;
+ selection_idx = selection_out;
+
+ //if (is_changed) update_connector_shape();
+ ImGuiWrapper::pop_combo_style();
+
+ return is_changed;
+}
+
GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: m_parent(parent)
, m_group_id(-1)
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
index 626b7989a8..e78da31501 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
@@ -149,6 +149,9 @@ protected:
bool m_is_dark_mode = false;
+ bool render_combo(const std::string &label, const std::vector &lines,
+ int &selection_idx, float label_width, float item_width);
+
public:
GLGizmoBase(GLCanvas3D& parent,
const std::string& icon_filename,
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
index c6fc3d445a..53e1ab1cad 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
@@ -520,40 +520,6 @@ bool GLGizmoCut3D::render_cut_mode_combo()
return is_changed;
}
-bool GLGizmoCut3D::render_combo(const std::string& label, const std::vector& lines, int& selection_idx)
-{
- ImGuiWrapper::push_combo_style(m_parent.get_scale());
- ImGui::AlignTextToFramePadding();
- m_imgui->text(label);
- ImGui::SameLine(m_label_width);
- ImGui::PushItemWidth(m_editing_window_width);
-
- size_t selection_out = selection_idx;
-
- const char* selected_str = (selection_idx >= 0 && selection_idx < int(lines.size())) ? lines[selection_idx].c_str() : "";
- if (ImGui::BBLBeginCombo(("##" + label).c_str(), selected_str, 0)) {
- for (size_t line_idx = 0; line_idx < lines.size(); ++line_idx) {
- ImGui::PushID(int(line_idx));
- if (ImGui::Selectable("", line_idx == selection_idx))
- selection_out = line_idx;
-
- ImGui::SameLine();
- ImGui::Text("%s", lines[line_idx].c_str());
- ImGui::PopID();
- }
-
- ImGui::EndCombo();
- }
-
- bool is_changed = selection_idx != selection_out;
- selection_idx = selection_out;
-
- //if (is_changed) update_connector_shape();
- ImGuiWrapper::pop_combo_style();
-
- return is_changed;
-}
-
bool GLGizmoCut3D::render_double_input(const std::string& label, double& value_in)
{
ImGui::AlignTextToFramePadding();
@@ -2299,7 +2265,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors, flo
m_connector_style = int(CutConnectorStyle::Prism);
apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); });
}
- if (render_combo(m_labels_map["Style"], m_connector_styles, m_connector_style))
+ if (render_combo(m_labels_map["Style"], m_connector_styles, m_connector_style, m_label_width, m_editing_window_width))
apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); });
m_imgui->disabled_end();
@@ -2308,7 +2274,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors, flo
m_connector_shape_id = int(CutConnectorShape::Circle);
apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); });
}
- if (render_combo(m_labels_map["Shape"], m_connector_shapes, m_connector_shape_id))
+ if (render_combo(m_labels_map["Shape"], m_connector_shapes, m_connector_shape_id, m_label_width, m_editing_window_width))
apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); });
m_imgui->disabled_end();
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp
index f09ca7a4b8..47a6d47f39 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp
@@ -335,7 +335,6 @@ private:
void set_center(const Vec3d¢er, bool update_tbb = false);
void switch_to_mode(size_t new_mode);
bool render_cut_mode_combo();
- bool render_combo(const std::string&label, const std::vector&lines, int&selection_idx);
bool render_double_input(const std::string& label, double& value_in);
bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float min_val = -0.1f, float max_tolerance = -0.1f);
void render_move_center_input(int axis);
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp
index 2405756bb7..406f88e6d2 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp
@@ -265,6 +265,10 @@ bool GLGizmoMeasure::on_mouse(const wxMouseEvent &mouse_event)
if (m_selected_features.first.feature.has_value()) {
reset_feature2_render();
const SelectedFeatures::Item item = detect_current_item();
+ if (!is_pick_meet_assembly_mode(item)) { // assembly deal
+ m_selected_wrong_feature_waring_tip = true;
+ return true;
+ }
m_selected_wrong_feature_waring_tip = false;
if (m_selected_features.first != item) {
bool processed = false;
@@ -329,6 +333,10 @@ bool GLGizmoMeasure::on_mouse(const wxMouseEvent &mouse_event)
// 1st feature selection
reset_feature1_render();
const SelectedFeatures::Item item = detect_current_item();
+ if (!is_pick_meet_assembly_mode(item)) {//assembly deal
+ m_selected_wrong_feature_waring_tip = true;
+ return true;
+ }
m_selected_wrong_feature_waring_tip = false;
m_selected_features.first = item;
if (requires_sphere_raycaster_for_picking(item)) {
@@ -2035,6 +2043,79 @@ void GLGizmoMeasure::show_distance_xyz_ui()
//{
//}
+void GLGizmoMeasure::show_face_face_assembly_common() {
+ if (m_measure_mode == EMeasureMode::ONLY_ASSEMBLY && m_hit_different_volumes.size() == 2 &&
+ m_selected_features.first.feature->get_type() == Measure::SurfaceFeatureType::Plane &&
+ m_selected_features.second.feature->get_type() == Measure::SurfaceFeatureType::Plane) {
+ auto &action = m_assembly_action;
+ auto set_to_parallel_size = m_imgui->calc_button_size(_L("Parallel")).x;
+ auto set_to_center_coincidence_size = m_imgui->calc_button_size(_L("Center coincidence")).x;
+
+ m_imgui->disabled_begin(!(action.can_set_to_center_coincidence));
+ {
+ ImGui::PushItemWidth(set_to_center_coincidence_size);
+ ImGui::PushStyleColor(ImGuiCol_Button, m_is_dark_mode ? ImVec4(0 / 255.0, 174 / 255.0, 66 / 255.0, 1.0) : ImVec4(0 / 255.0, 174 / 255.0, 66 / 255.0, 1.0));
+ ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
+ m_is_dark_mode ? ImVec4(50 / 255.0f, 238 / 255.0f, 61 / 255.0f, 1.00f) : ImVec4(50 / 255.0f, 238 / 255.0f, 61 / 255.0f, 1.00f));
+ ImGui::PushStyleColor(ImGuiCol_ButtonActive,
+ m_is_dark_mode ? ImVec4(206 / 255.0f, 206 / 255.0f, 206 / 255.0f, 1.00f) : ImVec4(206 / 255.0f, 206 / 255.0f, 206 / 255.0f, 1.00f));
+ ImGui::PushStyleColor(ImGuiCol_Text,
+ m_is_dark_mode ? ImVec4(255 / 255.0f, 255 / 255.0f, 255 / 255.0f, 1.00f) : ImVec4(255 / 255.0f, 255 / 255.0f, 255 / 255.0f, 1.00f));
+ if (m_imgui->button(_L("Center coincidence"))) {
+ set_to_center_coincidence(m_same_model_object);
+ }
+ ImGui::PopStyleColor(4);
+ ImGui::SameLine(set_to_center_coincidence_size + m_space_size * 2);
+ }
+ m_imgui->disabled_end();
+
+ m_imgui->disabled_begin(!action.can_set_to_parallel);
+ {
+ if (m_imgui->button(_L("Parallel"))) { set_to_parallel(m_same_model_object); }
+ }
+ m_imgui->disabled_end();
+ }
+}
+
+void GLGizmoMeasure::show_face_face_assembly_senior()
+{
+ if (m_measure_mode == EMeasureMode::ONLY_ASSEMBLY && m_hit_different_volumes.size() == 2 &&
+ m_selected_features.first.feature->get_type() == Measure::SurfaceFeatureType::Plane &&
+ m_selected_features.second.feature->get_type() == Measure::SurfaceFeatureType::Plane) {
+ auto &action = m_assembly_action;
+ auto feature_text_size = m_imgui->calc_button_size(_L("Featue 1")).x + m_imgui->calc_button_size(":").x;
+ auto set_to_reverse_rotation_size = m_imgui->calc_button_size(_L("Reverse rotation")).x;
+ auto rotate_around_center_size = m_imgui->calc_button_size(_L("Rotate around center:")).x;
+ auto parallel_distance_size = m_imgui->calc_button_size(_L("Parallel_distance:")).x;
+
+ if (m_imgui->bbl_checkbox(_L("Flip by Face 2"), m_flip_volume_2)) {
+ set_to_reverse_rotation(m_same_model_object, 1);
+ }
+
+ if (action.has_parallel_distance) {
+ m_imgui->text(_u8L("Parallel_distance:"));
+ ImGui::SameLine(parallel_distance_size + m_space_size);
+ ImGui::PushItemWidth(m_input_size_max);
+ ImGui::BBLInputDouble("##parallel_distance_z", &m_buffered_parallel_distance, 0.0f, 0.0f, "%.2f");
+ if (m_last_active_item_imgui != m_current_active_imgui_id && std::abs(m_buffered_parallel_distance - action.parallel_distance) > EPSILON) {
+ set_parallel_distance(m_same_model_object, m_buffered_parallel_distance);
+ }
+ }
+ if (action.can_around_center_of_faces) {
+ m_imgui->text(_u8L("Rotate around center:"));
+ ImGui::SameLine(rotate_around_center_size + m_space_size);
+ ImGui::PushItemWidth(m_input_size_max);
+ ImGui::BBLInputDouble("##rotate_around_center", &m_buffered_around_center, 0.0f, 0.0f, "%.2f");
+ if (m_last_active_item_imgui != m_current_active_imgui_id && std::abs(m_buffered_around_center) > EPSILON) {
+ set_to_around_center_of_faces(m_same_model_object, m_buffered_around_center);
+ m_buffered_around_center = 0;
+ }
+ ImGui::SameLine(rotate_around_center_size + m_space_size + m_input_size_max + m_space_size / 2.0f);
+ m_imgui->text(_L("°"));
+ }
+ }
+}
+
void GLGizmoMeasure::init_render_input_window()
{
m_use_inches = wxGetApp().app_config->get_bool("use_inches");
@@ -2101,6 +2182,10 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit
ImGuiWrapper::pop_toolbar_style();
}
+void GLGizmoMeasure::render_input_window_warning(bool same_model_object)
+{
+}
+
void GLGizmoMeasure::remove_selected_sphere_raycaster(int id)
{
reset_gripper_pick(id == SEL_SPHERE_1_ID ? GripperType::SPHERE_1 : GripperType::SPHERE_2);
@@ -2110,9 +2195,11 @@ void GLGizmoMeasure::update_measurement_result()
{
if (!m_selected_features.first.feature.has_value()) {
m_measurement_result = Measure::MeasurementResult();
+ m_assembly_action = Measure::AssemblyAction();
}
else if (m_selected_features.second.feature.has_value()) {
m_measurement_result = Measure::get_measurement(*m_selected_features.first.feature, *m_selected_features.second.feature, true);
+ m_assembly_action = Measure::get_assembly_action(*m_selected_features.first.feature, *m_selected_features.second.feature);
m_can_set_xyz_distance = Measure::can_set_xyz_distance(*m_selected_features.first.feature, *m_selected_features.second.feature);
//update buffer
const Measure::MeasurementResult &measure = m_measurement_result;
@@ -2126,6 +2213,9 @@ void GLGizmoMeasure::update_measurement_result()
}
if (wxGetApp().app_config->get_bool("use_inches")) m_distance = GizmoObjectManipulation::mm_to_in * m_distance;
m_buffered_distance = m_distance;
+ if (m_assembly_action.has_parallel_distance) {
+ m_buffered_parallel_distance = m_assembly_action.parallel_distance;
+ }
}
else if (!m_selected_features.second.feature.has_value() && m_selected_features.first.feature->get_type() == Measure::SurfaceFeatureType::Circle)
m_measurement_result = Measure::get_measurement(*m_selected_features.first.feature, Measure::SurfaceFeature(std::get<0>(m_selected_features.first.feature->get_circle())));
@@ -2399,5 +2489,201 @@ void GLGizmoMeasure::set_distance(bool same_model_object, const Vec3d &displacem
}
}
+void GLGizmoMeasure::set_to_parallel(bool same_model_object, bool take_shot, bool is_anti_parallel)
+{
+ if (m_hit_different_volumes.size() == 2) {
+ auto &action = m_assembly_action;
+ auto v = m_hit_different_volumes[1];
+ auto selection = const_cast(&m_parent.get_selection());
+ selection->setup_cache();
+ if (take_shot) {
+ wxGetApp().plater()->take_snapshot("RotateInMeasure", UndoRedo::SnapshotType::GizmoAction); // avoid storing another snapshot
+ }
+ selection->set_mode(same_model_object ? Selection::Volume : Selection::Instance);
+ const auto [idx1, normal1, pt1] = m_selected_features.first.feature->get_plane();
+ const auto [idx2, normal2, pt2] = m_selected_features.second.feature->get_plane();
+ if ((is_anti_parallel && normal1.dot(normal2) > -1 + 1e-3) ||
+ (is_anti_parallel == false && (normal1.dot(normal2) < 1 - 1e-3))) {
+ m_pending_scale ++;
+ Vec3d axis;
+ double angle;
+ Matrix3d rotation_matrix;
+ Geometry::rotation_from_two_vectors(normal2, -normal1, axis, angle, &rotation_matrix);
+ Transform3d r_m = (Transform3d) rotation_matrix;
+ if (same_model_object == false) {
+ auto new_rotation_tran = r_m * v->get_instance_transformation().get_rotation_matrix();
+ Vec3d rotation = Geometry::extract_euler_angles(new_rotation_tran);
+ v->set_instance_rotation(rotation);
+ selection->rotate(v->object_idx(), v->instance_idx(), v->get_instance_transformation().get_matrix());
+ } else {
+ Geometry::Transformation world_tran(v->world_matrix());
+ auto new_tran = r_m * world_tran.get_rotation_matrix();
+ Transform3d volume_rotation_tran = v->get_instance_transformation().get_rotation_matrix().inverse() * new_tran;
+ Vec3d rotation = Geometry::extract_euler_angles(volume_rotation_tran);
+ v->set_volume_rotation(rotation);
+ selection->rotate(v->object_idx(), v->instance_idx(), v->volume_idx(), v->get_volume_transformation().get_matrix());
+ }
+ wxGetApp().plater()->canvas3D()->do_rotate("");
+ register_single_mesh_pick();
+ if (same_model_object) {
+ update_feature_by_tran(*m_selected_features.first.feature);
+ }
+ update_feature_by_tran(*m_selected_features.second.feature);
+ }
+ }
+}
+
+void GLGizmoMeasure::set_to_reverse_rotation(bool same_model_object, int feature_index)
+{
+ if (m_hit_different_volumes.size() == 2 && feature_index < 2) {
+ auto &action = m_assembly_action;
+ auto v = m_hit_different_volumes[feature_index];
+ auto selection = const_cast(&m_parent.get_selection());
+ selection->setup_cache();
+ wxGetApp().plater()->take_snapshot("ReverseRotateInMeasure", UndoRedo::SnapshotType::GizmoAction); // avoid storing another snapshot
+
+ selection->set_mode(same_model_object ? Selection::Volume : Selection::Instance);
+ m_pending_scale = 1;
+ Vec3d plane_normal,plane_center;
+ if (feature_index ==0) {//feature 1
+ const auto [idx1, normal1, pt1] = m_selected_features.first.feature->get_plane();
+ plane_normal = normal1;
+ plane_center = pt1;
+ }
+ else { // feature 2
+ const auto [idx2, normal2, pt2] = m_selected_features.second.feature->get_plane();
+ plane_normal = normal2;
+ plane_center = pt2;
+ }
+ auto new_pt = Measure::get_one_point_in_plane(plane_center, plane_normal);
+ Vec3d axis = (new_pt - plane_center).normalized();
+ if (axis.norm() < 0.1) {
+ throw;
+ }
+ if (same_model_object == false) {
+ Geometry::Transformation inMat(v->get_instance_transformation());
+ Geometry::Transformation outMat = Geometry::mat_around_a_point_rotate(inMat, plane_center, axis, PI);
+ selection->rotate(v->object_idx(), v->instance_idx(), outMat.get_matrix());
+ } else {
+ Geometry::Transformation inMat(v->world_matrix());
+ Geometry::Transformation outMat = Geometry::mat_around_a_point_rotate(inMat, plane_center, axis, PI);
+ Transform3d volume_tran = v->get_instance_transformation().get_matrix().inverse() * outMat.get_matrix();
+ selection->rotate(v->object_idx(), v->instance_idx(), v->volume_idx(), volume_tran);
+ }
+ wxGetApp().plater()->canvas3D()->do_rotate("");
+ register_single_mesh_pick();
+ if (same_model_object == false) {
+ if (feature_index == 0) { // feature 1
+ update_feature_by_tran(*m_selected_features.first.feature);
+ } else { // feature 2
+ update_feature_by_tran(*m_selected_features.second.feature);
+ }
+ }
+ else {
+ update_feature_by_tran(*m_selected_features.first.feature);
+ update_feature_by_tran(*m_selected_features.second.feature);
+ }
+ }
+}
+
+void GLGizmoMeasure::set_to_around_center_of_faces(bool same_model_object, float rotate_degree)
+{
+ if (m_hit_different_volumes.size() == 2 ) {
+ auto &action = m_assembly_action;
+ auto v = m_hit_different_volumes[1];
+ auto selection = const_cast(&m_parent.get_selection());
+ selection->setup_cache();
+ wxGetApp().plater()->take_snapshot("ReverseRotateInMeasure", UndoRedo::SnapshotType::GizmoAction); // avoid storing another snapshot
+
+ selection->set_mode(same_model_object ? Selection::Volume : Selection::Instance);
+ m_pending_scale = 1;
+
+ auto radian = Geometry::deg2rad(rotate_degree);
+ Vec3d plane_normal, plane_center;
+ const auto [idx2, normal2, pt2] = m_selected_features.second.feature->get_plane();
+ plane_normal = normal2;
+ plane_center = pt2;
+
+ if (same_model_object == false) {
+ Geometry::Transformation inMat(v->get_instance_transformation());
+ Geometry::Transformation outMat = Geometry::mat_around_a_point_rotate(inMat, plane_center, plane_normal, radian);
+ selection->rotate(v->object_idx(), v->instance_idx(), outMat.get_matrix());
+ } else {
+ Geometry::Transformation inMat(v->world_matrix());
+ Geometry::Transformation outMat = Geometry::mat_around_a_point_rotate(inMat, plane_center, plane_normal, radian);
+ Transform3d volume_tran = v->get_instance_transformation().get_matrix().inverse() * outMat.get_matrix();
+ selection->rotate(v->object_idx(), v->instance_idx(), v->volume_idx(), volume_tran);
+ }
+ wxGetApp().plater()->canvas3D()->do_rotate("");
+ register_single_mesh_pick();
+ if (same_model_object) {
+ update_feature_by_tran(*m_selected_features.first.feature);
+ }
+ update_feature_by_tran(*m_selected_features.second.feature);
+ }
+}
+
+void GLGizmoMeasure::set_to_center_coincidence(bool same_model_object) {
+ auto v = m_hit_different_volumes[1];
+ wxGetApp().plater()->take_snapshot("RotateThenMoveInMeasure", UndoRedo::SnapshotType::GizmoAction);
+ set_to_parallel(same_model_object, false,true);
+
+ const auto [idx1, normal1, pt1] = m_selected_features.first.feature->get_plane();
+ const auto [idx2, normal2, pt2] = m_selected_features.second.feature->get_plane();
+ set_distance(same_model_object, pt1 - pt2, false);
+ m_set_center_coincidence = true;
+}
+
+void GLGizmoMeasure::set_parallel_distance(bool same_model_object, float dist)
+{
+ if (m_hit_different_volumes.size() == 2 && abs(dist) >= 0.0f) {
+ auto v = m_hit_different_volumes[1];
+ auto selection = const_cast(&m_parent.get_selection());
+ selection->setup_cache();
+
+ wxGetApp().plater()->take_snapshot("SetParallelDistanceInMeasure", UndoRedo::SnapshotType::GizmoAction); // avoid storing another snapshot
+
+ selection->set_mode(same_model_object ? Selection::Volume : Selection::Instance);
+ m_pending_scale = 1;
+
+ const auto [idx1, normal1, pt1] = m_selected_features.first.feature->get_plane();
+ const auto [idx2, normal2, pt2] = m_selected_features.second.feature->get_plane();
+ Vec3d proj_pt2;
+ Measure::get_point_projection_to_plane(pt2, pt1, normal1, proj_pt2);
+ auto new_pt2 = proj_pt2 + normal1 * dist;
+
+ Vec3d displacement = new_pt2 - pt2;
+
+ if (same_model_object == false) {
+ selection->translate(v->object_idx(), v->instance_idx(), displacement);
+ } else {
+ selection->translate(v->object_idx(), v->instance_idx(), v->volume_idx(), displacement);
+ }
+ wxGetApp().plater()->canvas3D()->do_move("");
+ register_single_mesh_pick();
+ if (same_model_object) {
+ update_feature_by_tran(*m_selected_features.first.feature);
+ }
+ update_feature_by_tran(*m_selected_features.second.feature);
+ }
+}
+
+bool GLGizmoMeasure::is_pick_meet_assembly_mode(const SelectedFeatures::Item &item) {
+ if (m_measure_mode == EMeasureMode::ONLY_ASSEMBLY) {
+ if (m_assembly_mode == AssemblyMode::FACE_FACE && item.feature->get_type() == Measure::SurfaceFeatureType::Plane) {
+ return true;
+ }
+ if (m_assembly_mode == AssemblyMode::POINT_POINT &&
+ (item.feature->get_type() == Measure::SurfaceFeatureType::Point||
+ item.feature->get_type() == Measure::SurfaceFeatureType::Circle)) {
+ return true;
+ }
+ return false;
+ }
+ else {
+ return true;
+ }
+}
+
} // namespace GUI
} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp
index 2d9eb93dea..7fd54603f3 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp
@@ -155,6 +155,7 @@ protected:
EMode m_mode{ EMode::FeatureSelection };
Measure::MeasurementResult m_measurement_result;
+ Measure::AssemblyAction m_assembly_action;
std::map> m_mesh_measure_map;
std::shared_ptr m_curr_measuring{nullptr};
@@ -198,6 +199,8 @@ protected:
unsigned int m_last_active_item_imgui{0};
Vec3d m_buffered_distance;
Vec3d m_distance;
+ double m_buffered_parallel_distance{0};
+ double m_buffered_around_center{0};
// used to keep the raycasters for point/center spheres
//std::vector> m_selected_sphere_raycasters;
std::optional m_curr_feature;
@@ -259,9 +262,12 @@ protected:
void show_selection_ui();
void show_distance_xyz_ui();
//void show_point_point_assembly();
+ void show_face_face_assembly_common();
+ void show_face_face_assembly_senior();
void init_render_input_window();
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
+ virtual void render_input_window_warning(bool same_model_object);
void remove_selected_sphere_raycaster(int id);
void update_measurement_result();
@@ -289,6 +295,13 @@ protected:
void update_world_plane_features(Measure::Measuring *cur_measuring, Measure::SurfaceFeature &feautre);
void update_feature_by_tran(Measure::SurfaceFeature & feature);
void set_distance(bool same_model_object, const Vec3d &displacement, bool take_shot = true);
+ void set_to_parallel(bool same_model_object, bool take_shot = true, bool is_anti_parallel = false);
+ void set_to_reverse_rotation(bool same_model_object,int feature_index);
+ void set_to_around_center_of_faces(bool same_model_object,float rotate_degree);
+ void set_to_center_coincidence(bool same_model_object);
+ void set_parallel_distance(bool same_model_object,float dist);
+
+ bool is_pick_meet_assembly_mode(const SelectedFeatures::Item& item);
protected:
// This map holds all translated description texts, so they can be easily referenced during layout calculations
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
index cb3d952cac..e379ea2f90 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
@@ -23,8 +23,8 @@
#include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoEmboss.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoSVG.hpp"
-#include "slic3r/GUI/Gizmos/GLGizmoMeasure.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmoAssembly.hpp"
#include "libslic3r/format.hpp"
#include "libslic3r/Model.hpp"
@@ -62,6 +62,7 @@ std::vector GLGizmosManager::get_selectable_idxs() const
if (m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView) {
for (size_t i = 0; i < m_gizmos.size(); ++i)
if (m_gizmos[i]->get_sprite_id() == (unsigned int) Measure ||
+ m_gizmos[i]->get_sprite_id() == (unsigned int) Assembly ||
m_gizmos[i]->get_sprite_id() == (unsigned int) MmuSegmentation)
out.push_back(i);
}
@@ -162,6 +163,9 @@ void GLGizmosManager::switch_gizmos_icon_filename()
case (EType::Measure):
gizmo->set_icon_filename(m_is_dark ? "toolbar_measure_dark.svg" : "toolbar_measure.svg");
break;
+ case (EType::Assembly):
+ gizmo->set_icon_filename(m_is_dark ? "toolbar_assembly_dark.svg" : "toolbar_assembly.svg");
+ break;
}
}
@@ -200,6 +204,7 @@ bool GLGizmosManager::init()
m_gizmos.emplace_back(new GLGizmoEmboss(m_parent, m_is_dark ? "toolbar_text_dark.svg" : "toolbar_text.svg", EType::Emboss));
m_gizmos.emplace_back(new GLGizmoSVG(m_parent));
m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, m_is_dark ? "toolbar_measure_dark.svg" : "toolbar_measure.svg", EType::Measure));
+ m_gizmos.emplace_back(new GLGizmoAssembly(m_parent, m_is_dark ? "toolbar_assembly_dark.svg" : "toolbar_assembly.svg", EType::Assembly));
m_gizmos.emplace_back(new GLGizmoSimplify(m_parent, "reduce_triangles.svg", EType::Simplify));
//m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", sprite_id++));
//m_gizmos.emplace_back(new GLGizmoFaceDetector(m_parent, "face recognition.svg", sprite_id++));
@@ -343,8 +348,8 @@ bool GLGizmosManager::check_gizmos_closed_except(EType type) const
void GLGizmosManager::set_hover_id(int id)
{
- // Measure handles hover by itself
- if (m_current == EType::Measure) { return; }
+ // Measure and assembly handles hover by itself
+ if (m_current == EType::Measure || m_current == EType::Assembly) { return; }
if (!m_enabled || m_current == Undefined)
return;
@@ -438,7 +443,9 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p
else if (m_current == MmuSegmentation)
return dynamic_cast(m_gizmos[MmuSegmentation].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == Measure)
- return dynamic_cast(m_gizmos[Measure].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
+ return dynamic_cast(m_gizmos[Measure].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
+ else if (m_current == Assembly)
+ return dynamic_cast(m_gizmos[Assembly].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == Cut)
return dynamic_cast(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == MeshBoolean)
@@ -701,7 +708,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
case WXK_ESCAPE:
{
if (m_current != Undefined) {
- if (m_current == Measure && gizmo_event(SLAGizmoEventType::Escape)) {
+ if ((m_current == Measure || m_current == Assembly) && gizmo_event(SLAGizmoEventType::Escape)) {
// do nothing
} else
//if ((m_current != SlaSupports) || !gizmo_event(SLAGizmoEventType::DiscardChanges))
@@ -740,7 +747,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
case WXK_BACK:
case WXK_DELETE: {
- if ((m_current == Cut || m_current == Measure) && gizmo_event(SLAGizmoEventType::Delete))
+ if ((m_current == Cut || m_current == Measure || m_current == Assembly) && gizmo_event(SLAGizmoEventType::Delete))
processed = true;
break;
}
@@ -838,7 +845,7 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
processed = true;
}
}*/
- if (m_current == Measure) {
+ if (m_current == Measure || m_current == Assembly) {
if (keyCode == WXK_CONTROL)
gizmo_event(SLAGizmoEventType::CtrlUp, Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.CmdDown());
else if (keyCode == WXK_SHIFT)
@@ -925,8 +932,7 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
// force extra frame to automatically update window size
wxGetApp().imgui()->set_requires_extra_frame();
}
- }
- else if (m_current == Measure) {
+ } else if (m_current == Measure || m_current == Assembly) {
if (keyCode == WXK_CONTROL)
gizmo_event(SLAGizmoEventType::CtrlDown, Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.CmdDown());
else if (keyCode == WXK_SHIFT)
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
index 4511f02119..305c767f43 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
@@ -86,6 +86,7 @@ public:
Emboss,
Svg,
Measure,
+ Assembly,
Simplify,
//SlaSupports,
// BBS
diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp
index 187ec03eb7..79feb0e1fc 100644
--- a/src/slic3r/GUI/Selection.cpp
+++ b/src/slic3r/GUI/Selection.cpp
@@ -1720,6 +1720,66 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co
this->set_bounding_boxes_dirty();
}
+void Selection::translate(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx, const Vec3d &displacement) {
+ 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);
+ }
+
+ this->set_bounding_boxes_dirty();
+}
+
+void Selection::rotate(unsigned int object_idx, unsigned int instance_idx, const Transform3d &overwrite_tran)
+{
+ 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.set_instance_transformation(overwrite_tran);
+ }
+ }
+
+ 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_transformation(overwrite_tran);
+ done.insert(j);
+ }
+ }
+ this->set_bounding_boxes_dirty();
+}
+void Selection::rotate(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx, const Transform3d &overwrite_tran)
+{
+ 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_transformation(overwrite_tran);
+ }
+ }
+ this->set_bounding_boxes_dirty();
+}
+
//BBS: add partplate related logic
void Selection::notify_instance_update(int object_idx, int instance_idx)
{
diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp
index 8fc0f8bc66..0e95b41009 100644
--- a/src/slic3r/GUI/Selection.hpp
+++ b/src/slic3r/GUI/Selection.hpp
@@ -344,6 +344,10 @@ 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);
+
+ 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);
//BBS: add partplate related logic
void notify_instance_update(int object_idx, int instance_idx);
// BBS