diff --git a/deps/deps-macos.cmake b/deps/deps-macos.cmake
index 790a65aeb..c7c6819e3 100644
--- a/deps/deps-macos.cmake
+++ b/deps/deps-macos.cmake
@@ -89,10 +89,12 @@ ExternalProject_Add(dep_libcurl
ExternalProject_Add(dep_wxwidgets
EXCLUDE_FROM_ALL 1
- URL "https://github.com/wxWidgets/wxWidgets/releases/download/v3.1.2/wxWidgets-3.1.2.tar.bz2"
- URL_HASH SHA256=4cb8d23d70f9261debf7d6cfeca667fc0a7d2b6565adb8f1c484f9b674f1f27a
+ GIT_REPOSITORY "https://github.com/prusa3d/wxWidgets"
+ GIT_TAG v3.1.1-patched
+# URL "https://github.com/wxWidgets/wxWidgets/releases/download/v3.1.2/wxWidgets-3.1.2.tar.bz2"
+# URL_HASH SHA256=4cb8d23d70f9261debf7d6cfeca667fc0a7d2b6565adb8f1c484f9b674f1f27a
BUILD_IN_SOURCE 1
- PATCH_COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/wxwidgets-pngprefix.h" src/png/pngprefix.h
+# PATCH_COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/wxwidgets-pngprefix.h" src/png/pngprefix.h
CONFIGURE_COMMAND env "CXXFLAGS=${DEP_WERRORS_SDK}" "CFLAGS=${DEP_WERRORS_SDK}" ./configure
"--prefix=${DESTDIR}/usr/local"
--disable-shared
diff --git a/resources/icons/layers.svg b/resources/icons/layers.svg
index cd71fab3a..da5dec21d 100644
--- a/resources/icons/layers.svg
+++ b/resources/icons/layers.svg
@@ -5,13 +5,13 @@
-
+
-
+
-
+
@@ -20,7 +20,7 @@
-
+
diff --git a/resources/icons/white/add_copies.svg b/resources/icons/white/add_copies.svg
new file mode 100644
index 000000000..17eff0179
--- /dev/null
+++ b/resources/icons/white/add_copies.svg
@@ -0,0 +1,19 @@
+
+
+
diff --git a/resources/icons/white/add_modifier.svg b/resources/icons/white/add_modifier.svg
new file mode 100644
index 000000000..09c3ce27d
--- /dev/null
+++ b/resources/icons/white/add_modifier.svg
@@ -0,0 +1,13 @@
+
+
+
diff --git a/resources/icons/white/add_part.svg b/resources/icons/white/add_part.svg
new file mode 100644
index 000000000..b7c800bbb
--- /dev/null
+++ b/resources/icons/white/add_part.svg
@@ -0,0 +1,20 @@
+
+
+
diff --git a/resources/icons/white/advanced+wrench.svg b/resources/icons/white/advanced+wrench.svg
new file mode 100644
index 000000000..5e878cb3c
--- /dev/null
+++ b/resources/icons/white/advanced+wrench.svg
@@ -0,0 +1,43 @@
+
+
+
diff --git a/resources/icons/white/advanced_plus.svg b/resources/icons/white/advanced_plus.svg
new file mode 100644
index 000000000..db532ec4b
--- /dev/null
+++ b/resources/icons/white/advanced_plus.svg
@@ -0,0 +1,24 @@
+
+
+
diff --git a/resources/icons/white/cog.svg b/resources/icons/white/cog.svg
new file mode 100644
index 000000000..773e4d65d
--- /dev/null
+++ b/resources/icons/white/cog.svg
@@ -0,0 +1,17 @@
+
+
+
diff --git a/resources/icons/white/cooling.svg b/resources/icons/white/cooling.svg
new file mode 100644
index 000000000..29bd04c36
--- /dev/null
+++ b/resources/icons/white/cooling.svg
@@ -0,0 +1,25 @@
+
+
+
diff --git a/resources/icons/white/copy_menu.svg b/resources/icons/white/copy_menu.svg
new file mode 100644
index 000000000..d660aab6a
--- /dev/null
+++ b/resources/icons/white/copy_menu.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/resources/icons/white/delete.svg b/resources/icons/white/delete.svg
new file mode 100644
index 000000000..91d5ce74c
--- /dev/null
+++ b/resources/icons/white/delete.svg
@@ -0,0 +1,22 @@
+
+
+
diff --git a/resources/icons/white/delete_all_menu.svg b/resources/icons/white/delete_all_menu.svg
new file mode 100644
index 000000000..5d825c424
--- /dev/null
+++ b/resources/icons/white/delete_all_menu.svg
@@ -0,0 +1,31 @@
+
+
+
diff --git a/resources/icons/white/editor_menu.svg b/resources/icons/white/editor_menu.svg
new file mode 100644
index 000000000..649d2c40f
--- /dev/null
+++ b/resources/icons/white/editor_menu.svg
@@ -0,0 +1,20 @@
+
+
+
diff --git a/resources/icons/white/export_config.svg b/resources/icons/white/export_config.svg
new file mode 100644
index 000000000..22f8ebe1a
--- /dev/null
+++ b/resources/icons/white/export_config.svg
@@ -0,0 +1,20 @@
+
+
+
diff --git a/resources/icons/white/export_config_bundle.svg b/resources/icons/white/export_config_bundle.svg
new file mode 100644
index 000000000..99bd62b6c
--- /dev/null
+++ b/resources/icons/white/export_config_bundle.svg
@@ -0,0 +1,50 @@
+
+
+
diff --git a/resources/icons/white/export_gcode.svg b/resources/icons/white/export_gcode.svg
new file mode 100644
index 000000000..39f5225cb
--- /dev/null
+++ b/resources/icons/white/export_gcode.svg
@@ -0,0 +1,14 @@
+
+
+
diff --git a/resources/icons/white/export_plate.svg b/resources/icons/white/export_plate.svg
new file mode 100644
index 000000000..18159d2a8
--- /dev/null
+++ b/resources/icons/white/export_plate.svg
@@ -0,0 +1,15 @@
+
+
+
diff --git a/resources/icons/white/export_plater.svg b/resources/icons/white/export_plater.svg
new file mode 100644
index 000000000..e71b38a0a
--- /dev/null
+++ b/resources/icons/white/export_plater.svg
@@ -0,0 +1,12 @@
+
+
+
diff --git a/resources/icons/white/extruder+funnel.svg b/resources/icons/white/extruder+funnel.svg
new file mode 100644
index 000000000..de6b227da
--- /dev/null
+++ b/resources/icons/white/extruder+funnel.svg
@@ -0,0 +1,15 @@
+
+
+
diff --git a/resources/icons/white/flag_green.svg b/resources/icons/white/flag_green.svg
new file mode 100644
index 000000000..8479d0e9a
--- /dev/null
+++ b/resources/icons/white/flag_green.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/resources/icons/white/flag_red.svg b/resources/icons/white/flag_red.svg
new file mode 100644
index 000000000..a9b1cf8fe
--- /dev/null
+++ b/resources/icons/white/flag_red.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/resources/icons/white/import_config.svg b/resources/icons/white/import_config.svg
new file mode 100644
index 000000000..001277f73
--- /dev/null
+++ b/resources/icons/white/import_config.svg
@@ -0,0 +1,25 @@
+
+
+
diff --git a/resources/icons/white/import_config_bundle.svg b/resources/icons/white/import_config_bundle.svg
new file mode 100644
index 000000000..c16cd7b3c
--- /dev/null
+++ b/resources/icons/white/import_config_bundle.svg
@@ -0,0 +1,61 @@
+
+
+
diff --git a/resources/icons/white/import_plate.svg b/resources/icons/white/import_plate.svg
new file mode 100644
index 000000000..7f888e3d7
--- /dev/null
+++ b/resources/icons/white/import_plate.svg
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+]>
+
diff --git a/resources/icons/white/import_plater.svg b/resources/icons/white/import_plater.svg
new file mode 100644
index 000000000..1dc5aae6e
--- /dev/null
+++ b/resources/icons/white/import_plater.svg
@@ -0,0 +1,17 @@
+
+
+
diff --git a/resources/icons/white/infill.svg b/resources/icons/white/infill.svg
new file mode 100644
index 000000000..086043099
--- /dev/null
+++ b/resources/icons/white/infill.svg
@@ -0,0 +1,35 @@
+
+
+
diff --git a/resources/icons/white/layers.svg b/resources/icons/white/layers.svg
new file mode 100644
index 000000000..cd71fab3a
--- /dev/null
+++ b/resources/icons/white/layers.svg
@@ -0,0 +1,27 @@
+
+
+
diff --git a/resources/icons/white/lock2_closed.svg b/resources/icons/white/lock2_closed.svg
new file mode 100644
index 000000000..726c850a1
--- /dev/null
+++ b/resources/icons/white/lock2_closed.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/resources/icons/white/lock_closed.svg b/resources/icons/white/lock_closed.svg
new file mode 100644
index 000000000..1665dc9a0
--- /dev/null
+++ b/resources/icons/white/lock_closed.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/resources/icons/white/machine+cog.svg b/resources/icons/white/machine+cog.svg
new file mode 100644
index 000000000..ec49265b8
--- /dev/null
+++ b/resources/icons/white/machine+cog.svg
@@ -0,0 +1,30 @@
+
+
+
diff --git a/resources/icons/white/notes.svg b/resources/icons/white/notes.svg
new file mode 100644
index 000000000..25de4191e
--- /dev/null
+++ b/resources/icons/white/notes.svg
@@ -0,0 +1,25 @@
+
+
+
diff --git a/resources/icons/white/number_of_copies.svg b/resources/icons/white/number_of_copies.svg
new file mode 100644
index 000000000..7c9d78a80
--- /dev/null
+++ b/resources/icons/white/number_of_copies.svg
@@ -0,0 +1,29 @@
+
+
+
diff --git a/resources/icons/white/open.svg b/resources/icons/white/open.svg
new file mode 100644
index 000000000..0ea9ecdb2
--- /dev/null
+++ b/resources/icons/white/open.svg
@@ -0,0 +1,11 @@
+
+
+
diff --git a/resources/icons/white/output+page_white.svg b/resources/icons/white/output+page_white.svg
new file mode 100644
index 000000000..083874003
--- /dev/null
+++ b/resources/icons/white/output+page_white.svg
@@ -0,0 +1,20 @@
+
+
+
diff --git a/resources/icons/white/pad.svg b/resources/icons/white/pad.svg
new file mode 100644
index 000000000..cddb2da02
--- /dev/null
+++ b/resources/icons/white/pad.svg
@@ -0,0 +1,83 @@
+
+
+
diff --git a/resources/icons/white/paste_menu.svg b/resources/icons/white/paste_menu.svg
new file mode 100644
index 000000000..465c2faf0
--- /dev/null
+++ b/resources/icons/white/paste_menu.svg
@@ -0,0 +1,27 @@
+
+
+
diff --git a/resources/icons/white/plater.svg b/resources/icons/white/plater.svg
new file mode 100644
index 000000000..d637a5e7e
--- /dev/null
+++ b/resources/icons/white/plater.svg
@@ -0,0 +1,12 @@
+
+
+
diff --git a/resources/icons/white/preview_menu.svg b/resources/icons/white/preview_menu.svg
new file mode 100644
index 000000000..98095359c
--- /dev/null
+++ b/resources/icons/white/preview_menu.svg
@@ -0,0 +1,48 @@
+
+
+
diff --git a/resources/icons/white/printer.svg b/resources/icons/white/printer.svg
new file mode 100644
index 000000000..d94f6fd5c
--- /dev/null
+++ b/resources/icons/white/printer.svg
@@ -0,0 +1,14 @@
+
+
+
diff --git a/resources/icons/white/re_slice.svg b/resources/icons/white/re_slice.svg
new file mode 100644
index 000000000..b8d7dc727
--- /dev/null
+++ b/resources/icons/white/re_slice.svg
@@ -0,0 +1,19 @@
+
+
+
diff --git a/resources/icons/white/remove_copies.svg b/resources/icons/white/remove_copies.svg
new file mode 100644
index 000000000..5b277e45e
--- /dev/null
+++ b/resources/icons/white/remove_copies.svg
@@ -0,0 +1,15 @@
+
+
+
diff --git a/resources/icons/white/remove_menu.svg b/resources/icons/white/remove_menu.svg
new file mode 100644
index 000000000..59360a33f
--- /dev/null
+++ b/resources/icons/white/remove_menu.svg
@@ -0,0 +1,44 @@
+
+
+
diff --git a/resources/icons/white/resin.svg b/resources/icons/white/resin.svg
new file mode 100644
index 000000000..81abfae0c
--- /dev/null
+++ b/resources/icons/white/resin.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/resources/icons/white/save.svg b/resources/icons/white/save.svg
new file mode 100644
index 000000000..3349a42dd
--- /dev/null
+++ b/resources/icons/white/save.svg
@@ -0,0 +1,25 @@
+
+
+
diff --git a/resources/icons/white/set_separate_obj.svg b/resources/icons/white/set_separate_obj.svg
new file mode 100644
index 000000000..de083c1c9
--- /dev/null
+++ b/resources/icons/white/set_separate_obj.svg
@@ -0,0 +1,29 @@
+
+
+
diff --git a/resources/icons/white/skirt+brim.svg b/resources/icons/white/skirt+brim.svg
new file mode 100644
index 000000000..684e177d2
--- /dev/null
+++ b/resources/icons/white/skirt+brim.svg
@@ -0,0 +1,15 @@
+
+
+
diff --git a/resources/icons/white/split_object_SMALL.svg b/resources/icons/white/split_object_SMALL.svg
new file mode 100644
index 000000000..4795cab51
--- /dev/null
+++ b/resources/icons/white/split_object_SMALL.svg
@@ -0,0 +1,19 @@
+
+
+
diff --git a/resources/icons/white/split_parts_SMALL.svg b/resources/icons/white/split_parts_SMALL.svg
new file mode 100644
index 000000000..eba846c17
--- /dev/null
+++ b/resources/icons/white/split_parts_SMALL.svg
@@ -0,0 +1,18 @@
+
+
+
diff --git a/resources/icons/white/spool.svg b/resources/icons/white/spool.svg
new file mode 100644
index 000000000..7c9fbab97
--- /dev/null
+++ b/resources/icons/white/spool.svg
@@ -0,0 +1,21 @@
+
+
+
diff --git a/resources/icons/white/support.svg b/resources/icons/white/support.svg
new file mode 100644
index 000000000..52b7c6b40
--- /dev/null
+++ b/resources/icons/white/support.svg
@@ -0,0 +1,94 @@
+
+
+
diff --git a/resources/icons/white/support_blocker.svg b/resources/icons/white/support_blocker.svg
new file mode 100644
index 000000000..17401e2ab
--- /dev/null
+++ b/resources/icons/white/support_blocker.svg
@@ -0,0 +1,48 @@
+
+
+
diff --git a/resources/icons/white/support_enforcer.svg b/resources/icons/white/support_enforcer.svg
new file mode 100644
index 000000000..0de2dc6a7
--- /dev/null
+++ b/resources/icons/white/support_enforcer.svg
@@ -0,0 +1,19 @@
+
+
+
diff --git a/resources/icons/white/test.svg b/resources/icons/white/test.svg
new file mode 100644
index 000000000..639bbbde8
--- /dev/null
+++ b/resources/icons/white/test.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/resources/icons/white/time.svg b/resources/icons/white/time.svg
new file mode 100644
index 000000000..d014286b5
--- /dev/null
+++ b/resources/icons/white/time.svg
@@ -0,0 +1,16 @@
+
+
+
diff --git a/resources/icons/white/upload_queue.svg b/resources/icons/white/upload_queue.svg
new file mode 100644
index 000000000..710e2be89
--- /dev/null
+++ b/resources/icons/white/upload_queue.svg
@@ -0,0 +1,29 @@
+
+
+
diff --git a/resources/icons/white/wrench.svg b/resources/icons/white/wrench.svg
new file mode 100644
index 000000000..714c5a82a
--- /dev/null
+++ b/resources/icons/white/wrench.svg
@@ -0,0 +1,20 @@
+
+
+
diff --git a/resources/icons/wrench_white.svg b/resources/icons/wrench_white.svg
new file mode 100644
index 000000000..714c5a82a
--- /dev/null
+++ b/resources/icons/wrench_white.svg
@@ -0,0 +1,20 @@
+
+
+
diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp
index ea3d87888..45f04b1df 100644
--- a/src/libslic3r/Technologies.hpp
+++ b/src/libslic3r/Technologies.hpp
@@ -11,6 +11,8 @@
#define ENABLE_SELECTION_DEBUG_OUTPUT 0
// Renders a small sphere in the center of the bounding box of the current selection when no gizmo is active
#define ENABLE_RENDER_SELECTION_CENTER 0
+// Shows an imgui dialog with render related data
+#define ENABLE_RENDER_STATISTICS 0
//====================
diff --git a/src/slic3r.cpp b/src/slic3r.cpp
index c3e69a189..e8f92e66d 100644
--- a/src/slic3r.cpp
+++ b/src/slic3r.cpp
@@ -628,7 +628,7 @@ std::string CLI::output_filepath(const Model &model, IO::ExportFormat format) co
{
std::string ext;
switch (format) {
- case IO::AMF: ext = ".amf"; break;
+ case IO::AMF: ext = ".zip.amf"; break;
case IO::OBJ: ext = ".obj"; break;
case IO::STL: ext = ".stl"; break;
case IO::TMF: ext = ".3mf"; break;
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 555918765..570e23baa 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -47,6 +47,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Gizmos/GLGizmoFlatten.hpp
GUI/Gizmos/GLGizmoCut.cpp
GUI/Gizmos/GLGizmoCut.hpp
+ GUI/GLSelectionRectangle.cpp
+ GUI/GLSelectionRectangle.hpp
GUI/GLTexture.hpp
GUI/GLTexture.cpp
GUI/GLToolbar.hpp
diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp
index c5ede9792..9d120132d 100644
--- a/src/slic3r/GUI/3DScene.cpp
+++ b/src/slic3r/GUI/3DScene.cpp
@@ -223,7 +223,8 @@ void GLIndexedVertexArray::render(
}
const float GLVolume::SELECTED_COLOR[4] = { 0.0f, 1.0f, 0.0f, 1.0f };
-const float GLVolume::HOVER_COLOR[4] = { 0.4f, 0.9f, 0.1f, 1.0f };
+const float GLVolume::HOVER_SELECT_COLOR[4] = { 0.4f, 0.9f, 0.1f, 1.0f };
+const float GLVolume::HOVER_DESELECT_COLOR[4] = { 1.0f, 0.75f, 0.75f, 1.0f };
const float GLVolume::OUTSIDE_COLOR[4] = { 0.0f, 0.38f, 0.8f, 1.0f };
const float GLVolume::SELECTED_OUTSIDE_COLOR[4] = { 0.19f, 0.58f, 1.0f, 1.0f };
const float GLVolume::DISABLED_COLOR[4] = { 0.25f, 0.25f, 0.25f, 1.0f };
@@ -251,7 +252,7 @@ GLVolume::GLVolume(float r, float g, float b, float a)
, zoom_to_volumes(true)
, shader_outside_printer_detection_enabled(false)
, is_outside(false)
- , hover(false)
+ , hover(HS_None)
, is_modifier(false)
, is_wipe_tower(false)
, is_extrusion_path(false)
@@ -291,10 +292,12 @@ void GLVolume::set_render_color()
if (force_native_color)
set_render_color(color, 4);
else {
- if (selected)
+ if (hover == HS_Select)
+ set_render_color(HOVER_SELECT_COLOR, 4);
+ else if (hover == HS_Deselect)
+ set_render_color(HOVER_DESELECT_COLOR, 4);
+ else if (selected)
set_render_color(is_outside ? SELECTED_OUTSIDE_COLOR : SELECTED_COLOR, 4);
- else if (hover)
- set_render_color(HOVER_COLOR, 4);
else if (disabled)
set_render_color(DISABLED_COLOR, 4);
else if (is_outside && shader_outside_printer_detection_enabled)
diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp
index 191b6a016..fc2126d3e 100644
--- a/src/slic3r/GUI/3DScene.hpp
+++ b/src/slic3r/GUI/3DScene.hpp
@@ -225,7 +225,8 @@ private:
class GLVolume {
public:
static const float SELECTED_COLOR[4];
- static const float HOVER_COLOR[4];
+ static const float HOVER_SELECT_COLOR[4];
+ static const float HOVER_DESELECT_COLOR[4];
static const float OUTSIDE_COLOR[4];
static const float SELECTED_OUTSIDE_COLOR[4];
static const float DISABLED_COLOR[4];
@@ -233,6 +234,13 @@ public:
static const float SLA_SUPPORT_COLOR[4];
static const float SLA_PAD_COLOR[4];
+ enum EHoverState : unsigned char
+ {
+ HS_None,
+ HS_Select,
+ HS_Deselect
+ };
+
GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f);
GLVolume(const float *rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {}
~GLVolume();
@@ -298,8 +306,8 @@ public:
bool shader_outside_printer_detection_enabled;
// Wheter or not this volume is outside print volume.
bool is_outside;
- // Boolean: Is mouse over this object?
- bool hover;
+ // Is mouse or rectangle selection over this object to select/deselect it ?
+ EHoverState hover;
// Wheter or not this volume has been generated from a modifier
bool is_modifier;
// Wheter or not this volume has been generated from the wipe tower
diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp
index 7a1023e62..dd6cbefe1 100644
--- a/src/slic3r/GUI/Camera.cpp
+++ b/src/slic3r/GUI/Camera.cpp
@@ -25,6 +25,7 @@ Camera::Camera()
, phi(45.0f)
// , distance(0.0f)
, requires_zoom_to_bed(false)
+ , inverted_phi(false)
, m_theta(45.0f)
, m_target(Vec3d::Zero())
{
diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp
index d19bc870e..b9c5dfc32 100644
--- a/src/slic3r/GUI/Camera.hpp
+++ b/src/slic3r/GUI/Camera.hpp
@@ -22,6 +22,7 @@ struct Camera
float phi;
// float distance;
bool requires_zoom_to_bed;
+ bool inverted_phi;
private:
Vec3d m_target;
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index 09ef2b2ac..d6013c791 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -52,6 +52,9 @@
#include
#include
#include
+#if ENABLE_RENDER_STATISTICS
+#include
+#endif // ENABLE_RENDER_STATISTICS
static const float TRACKBALLSIZE = 0.8f;
static const float GROUND_Z = -0.02f;
@@ -672,6 +675,7 @@ GLCanvas3D::Mouse::Mouse()
: dragging(false)
, position(DBL_MAX, DBL_MAX)
, scene_position(DBL_MAX, DBL_MAX, DBL_MAX)
+ , ignore_left_up(false)
{
}
@@ -1228,7 +1232,6 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar
, m_initialized(false)
, m_use_VBOs(false)
, m_apply_zoom_to_volumes_filter(false)
- , m_hover_volume_id(-1)
, m_legend_texture_enabled(false)
, m_picking_enabled(false)
, m_moving_enabled(false)
@@ -1237,6 +1240,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar
, m_regenerate_volumes(true)
, m_moving(false)
, m_tab_down(false)
+ , m_cursor_type(Standard)
, m_color_by("volume")
, m_reload_delayed(false)
, m_render_sla_auxiliaries(true)
@@ -1588,6 +1592,10 @@ void GLCanvas3D::render()
if (!_set_current() || !_3DScene::init(m_canvas))
return;
+#if ENABLE_RENDER_STATISTICS
+ auto start_time = std::chrono::high_resolution_clock::now();
+#endif // ENABLE_RENDER_STATISTICS
+
if (m_bed.get_shape().empty())
{
// this happens at startup when no data is still saved under <>\AppData\Roaming\Slic3rPE
@@ -1617,8 +1625,15 @@ void GLCanvas3D::render()
wxGetApp().imgui()->new_frame();
- // picking pass
- _picking_pass();
+ if (m_picking_enabled)
+ {
+ if (m_rectangle_selection.is_dragging())
+ // picking pass using rectangle selection
+ _rectangular_selection_picking_pass();
+ else
+ // regular picking pass
+ _picking_pass();
+ }
// draw scene
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
@@ -1654,6 +1669,9 @@ void GLCanvas3D::render()
_render_camera_target();
#endif // ENABLE_SHOW_CAMERA_TARGET
+ if (m_picking_enabled && m_rectangle_selection.is_dragging())
+ m_rectangle_selection.render(*this);
+
// draw overlays
_render_gizmos_overlay();
_render_warning_texture();
@@ -1666,9 +1684,26 @@ void GLCanvas3D::render()
if ((m_layers_editing.last_object_id >= 0) && (m_layers_editing.object_max_z() > 0.0f))
m_layers_editing.render_overlay(*this);
+#if ENABLE_RENDER_STATISTICS
+ ImGuiWrapper& imgui = *wxGetApp().imgui();
+ imgui.set_next_window_bg_alpha(0.5f);
+ imgui.begin(std::string("Render statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
+ imgui.text("Last frame: ");
+ ImGui::SameLine();
+ imgui.text(std::to_string(m_render_stats.last_frame));
+ ImGui::SameLine();
+ imgui.text(" ms");
+ imgui.end();
+#endif // ENABLE_RENDER_STATISTICS
+
wxGetApp().imgui()->render();
m_canvas->SwapBuffers();
+
+#if ENABLE_RENDER_STATISTICS
+ auto end_time = std::chrono::high_resolution_clock::now();
+ m_render_stats.last_frame = std::chrono::duration_cast(end_time - start_time).count();
+#endif // ENABLE_RENDER_STATISTICS
}
void GLCanvas3D::select_all()
@@ -2365,9 +2400,51 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
// m_canvas->HandleAsNavigationKey(evt); // XXX: Doesn't work in some cases / on Linux
post_event(SimpleEvent(EVT_GLCANVAS_TAB));
}
+ else if (keyCode == WXK_SHIFT)
+ {
+ if (m_picking_enabled && m_rectangle_selection.is_dragging())
+ {
+ _update_selection_from_hover();
+ m_rectangle_selection.stop_dragging();
+ m_mouse.ignore_left_up = true;
+ m_dirty = true;
+ }
+// set_cursor(Standard);
+ }
+ else if (keyCode == WXK_ALT)
+ {
+ if (m_picking_enabled && m_rectangle_selection.is_dragging())
+ {
+ _update_selection_from_hover();
+ m_rectangle_selection.stop_dragging();
+ m_mouse.ignore_left_up = true;
+ m_dirty = true;
+ }
+// set_cursor(Standard);
+ }
+ else if (keyCode == WXK_CONTROL)
+ m_dirty = true;
}
else if (evt.GetEventType() == wxEVT_KEY_DOWN) {
m_tab_down = keyCode == WXK_TAB && !evt.HasAnyModifiers();
+ if (keyCode == WXK_SHIFT)
+ {
+ if (m_picking_enabled && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports))
+ {
+ m_mouse.ignore_left_up = false;
+// set_cursor(Cross);
+ }
+ }
+ else if (keyCode == WXK_ALT)
+ {
+ if (m_picking_enabled && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports))
+ {
+ m_mouse.ignore_left_up = false;
+// set_cursor(Cross);
+ }
+ }
+ else if (keyCode == WXK_CONTROL)
+ m_dirty = true;
}
}
}
@@ -2486,6 +2563,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_mouse.set_start_position_3D_as_invalid();
m_mouse.set_start_position_2D_as_invalid();
m_mouse.dragging = false;
+ m_mouse.ignore_left_up = false;
m_dirty = true;
if (m_canvas->HasCapture())
@@ -2584,7 +2662,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (top_level_wnd && top_level_wnd->IsActive())
m_canvas->SetFocus();
m_mouse.position = pos.cast();
- // 1) forces a frame render to ensure that m_hover_volume_id is updated even when the user right clicks while
+ // 1) forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while
// the context menu is shown, ensuring it to disappear if the mouse is outside any volume and to
// change the volume hover state if any is under the mouse
// 2) when switching between 3d view and preview the size of the canvas changes if the side panels are visible,
@@ -2624,26 +2702,35 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_dirty = true;
}
}
+ else if (evt.LeftDown() && (evt.ShiftDown() || evt.AltDown()) && m_picking_enabled)
+ {
+ if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)
+ {
+ m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect);
+ m_dirty = true;
+ }
+ }
else
{
// Select volume in this 3D canvas.
// Don't deselect a volume if layer editing is enabled. We want the object to stay selected
// during the scene manipulation.
- if (m_picking_enabled && ((m_hover_volume_id != -1) || !is_layers_editing_enabled()))
+ if (m_picking_enabled && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled()))
{
- if (evt.LeftDown() && (m_hover_volume_id != -1))
+ if (evt.LeftDown() && !m_hover_volume_idxs.empty())
{
- bool already_selected = m_selection.contains_volume(m_hover_volume_id);
+ int volume_idx = get_first_hover_volume_idx();
+ bool already_selected = m_selection.contains_volume(volume_idx);
bool ctrl_down = evt.CmdDown();
Selection::IndicesList curr_idxs = m_selection.get_volume_idxs();
if (already_selected && ctrl_down)
- m_selection.remove(m_hover_volume_id);
+ m_selection.remove(volume_idx);
else
{
- m_selection.add(m_hover_volume_id, !ctrl_down, true);
+ m_selection.add(volume_idx, !ctrl_down, true);
m_mouse.drag.move_requires_threshold = !already_selected;
if (already_selected)
m_mouse.set_move_start_threshold_position_2D_as_invalid();
@@ -2651,6 +2738,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_mouse.drag.move_start_threshold_position_2D = pos;
}
+ // propagate event through callback
if (curr_idxs != m_selection.get_volume_idxs())
{
m_gizmos.refresh_on_off_state(m_selection);
@@ -2661,18 +2749,18 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
}
}
- // propagate event through callback
- if (m_hover_volume_id != -1)
+ if (!m_hover_volume_idxs.empty())
{
if (evt.LeftDown() && m_moving_enabled && (m_mouse.drag.move_volume_idx == -1))
{
// Only accept the initial position, if it is inside the volume bounding box.
- BoundingBoxf3 volume_bbox = m_volumes.volumes[m_hover_volume_id]->transformed_bounding_box();
+ int volume_idx = get_first_hover_volume_idx();
+ BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box();
volume_bbox.offset(1.0);
if (volume_bbox.contains(m_mouse.scene_position))
{
// The dragging operation is initiated.
- m_mouse.drag.move_volume_idx = m_hover_volume_id;
+ m_mouse.drag.move_volume_idx = volume_idx;
m_selection.start_dragging();
m_mouse.drag.start_position_3D = m_mouse.scene_position;
m_moving = true;
@@ -2689,7 +2777,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
Vec3d cur_pos = m_mouse.drag.start_position_3D;
// we do not want to translate objects if the user just clicked on an object while pressing shift to remove it from the selection and then drag
- if (m_selection.contains_volume(m_hover_volume_id))
+ if (m_selection.contains_volume(get_first_hover_volume_idx()))
{
if (m_camera.get_theta() == 90.0f)
{
@@ -2727,10 +2815,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_regenerate_volumes = false;
m_selection.translate(cur_pos - m_mouse.drag.start_position_3D);
wxGetApp().obj_manipul()->set_dirty();
-
m_dirty = true;
}
}
+ else if (evt.Dragging() && evt.LeftIsDown() && m_picking_enabled && m_rectangle_selection.is_dragging())
+ {
+ m_rectangle_selection.dragging(pos.cast());
+ m_dirty = true;
+ }
else if (evt.Dragging())
{
m_mouse.dragging = true;
@@ -2744,10 +2836,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
else if (evt.LeftIsDown())
{
// if dragging over blank area with left button, rotate
- if ((m_hover_volume_id == -1) && m_mouse.is_start_position_3D_defined())
+ if (m_hover_volume_idxs.empty() && m_mouse.is_start_position_3D_defined())
{
const Vec3d& orig = m_mouse.drag.start_position_3D;
- m_camera.phi += (((float)pos(0) - (float)orig(0)) * TRACKBALLSIZE);
+ float sign = m_camera.inverted_phi ? -1.0f : 1.0f;
+ m_camera.phi += sign * ((float)pos(0) - (float)orig(0)) * TRACKBALLSIZE;
m_camera.set_theta(m_camera.get_theta() - ((float)pos(1) - (float)orig(1)) * TRACKBALLSIZE, wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA);
m_dirty = true;
}
@@ -2786,7 +2879,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
// of the scene with the background processing data should be performed.
post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
}
- else if (evt.LeftUp() && !m_mouse.dragging && (m_hover_volume_id == -1) && !is_layers_editing_enabled())
+ else if (evt.LeftUp() && m_picking_enabled && m_rectangle_selection.is_dragging())
+ {
+ if (evt.ShiftDown() || evt.AltDown())
+ _update_selection_from_hover();
+
+ m_rectangle_selection.stop_dragging();
+ }
+ else if (evt.LeftUp() && !m_mouse.ignore_left_up && !m_mouse.dragging && m_hover_volume_idxs.empty() && !is_layers_editing_enabled())
{
// deselect and propagate event through callback
if (!evt.ShiftDown() && m_picking_enabled)
@@ -2799,21 +2899,24 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
}
}
+ else if (evt.LeftUp() && m_mouse.dragging)
+ // Flips X mouse deltas if bed is upside down
+ m_camera.inverted_phi = (m_camera.get_dir_up()(2) < 0.0);
else if (evt.RightUp())
{
m_mouse.position = pos.cast();
- // forces a frame render to ensure that m_hover_volume_id is updated even when the user right clicks while
+ // forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while
// the context menu is already shown
render();
- if (m_hover_volume_id != -1)
+ if (!m_hover_volume_idxs.empty())
{
// if right clicking on volume, propagate event through callback (shows context menu)
- if (m_volumes.volumes[m_hover_volume_id]->hover
- && !m_volumes.volumes[m_hover_volume_id]->is_wipe_tower // no context menu for the wipe tower
+ int volume_idx = get_first_hover_volume_idx();
+ if (!m_volumes.volumes[volume_idx]->is_wipe_tower // no context menu for the wipe tower
&& m_gizmos.get_current_type() != GLGizmosManager::SlaSupports) // disable context menu when the gizmo is open
{
// forces the selection of the volume
- m_selection.add(m_hover_volume_id);
+ m_selection.add(volume_idx);
m_gizmos.refresh_on_off_state(m_selection);
post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
m_gizmos.update_data(*this);
@@ -3213,6 +3316,20 @@ double GLCanvas3D::get_size_proportional_to_max_bed_size(double factor) const
return factor * m_bed.get_bounding_box().max_size();
}
+void GLCanvas3D::set_cursor(ECursorType type)
+{
+ if ((m_canvas != nullptr) && (m_cursor_type != type))
+ {
+ switch (type)
+ {
+ case Standard: { m_canvas->SetCursor(*wxSTANDARD_CURSOR); break; }
+ case Cross: { m_canvas->SetCursor(*wxCROSS_CURSOR); break; }
+ }
+
+ m_cursor_type = type;
+ }
+}
+
void GLCanvas3D::msw_rescale()
{
m_warning_texture.msw_rescale(*this);
@@ -3391,7 +3508,7 @@ bool GLCanvas3D::_init_toolbar()
item.name = "layersediting";
#if ENABLE_SVG_ICONS
- item.icon_filename = "layers.svg";
+ item.icon_filename = "layers_white.svg";
#endif // ENABLE_SVG_ICONS
item.tooltip = GUI::L_str("Layers editing");
item.sprite_id = 10;
@@ -3583,10 +3700,10 @@ void GLCanvas3D::_refresh_if_shown_on_screen()
void GLCanvas3D::_picking_pass() const
{
- const Vec2d& pos = m_mouse.position;
-
- if (m_picking_enabled && !m_mouse.dragging && (pos != Vec2d(DBL_MAX, DBL_MAX)))
+ if (m_picking_enabled && !m_mouse.dragging && (m_mouse.position != Vec2d(DBL_MAX, DBL_MAX)))
{
+ m_hover_volume_idxs.clear();
+
// Render the object for picking.
// FIXME This cannot possibly work in a multi - sampled context as the color gets mangled by the anti - aliasing.
// Better to use software ray - casting on a bounding - box hierarchy.
@@ -3617,27 +3734,98 @@ void GLCanvas3D::_picking_pass() const
GLubyte color[4] = { 0, 0, 0, 0 };
const Size& cnv_size = get_canvas_size();
- bool inside = (0 <= pos(0)) && (pos(0) < cnv_size.get_width()) && (0 <= pos(1)) && (pos(1) < cnv_size.get_height());
+ bool inside = (0 <= m_mouse.position(0)) && (m_mouse.position(0) < cnv_size.get_width()) && (0 <= m_mouse.position(1)) && (m_mouse.position(1) < cnv_size.get_height());
if (inside)
{
- glsafe(::glReadPixels(pos(0), cnv_size.get_height() - pos(1) - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color));
- volume_id = color[0] + color[1] * 256 + color[2] * 256 * 256;
+ glsafe(::glReadPixels(m_mouse.position(0), cnv_size.get_height() - m_mouse.position(1) - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color));
+ volume_id = color[0] + (color[1] << 8) + (color[2] << 16);
}
if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size()))
{
- m_hover_volume_id = volume_id;
+ m_hover_volume_idxs.push_back(volume_id);
m_gizmos.set_hover_id(-1);
}
else
- {
- m_hover_volume_id = -1;
m_gizmos.set_hover_id(inside && volume_id <= GLGizmoBase::BASE_ID ? (GLGizmoBase::BASE_ID - volume_id) : -1);
- }
_update_volumes_hover_state();
}
}
+void GLCanvas3D::_rectangular_selection_picking_pass() const
+{
+ m_gizmos.set_hover_id(-1);
+
+ std::set idxs;
+
+ if (m_picking_enabled)
+ {
+ if (m_multisample_allowed)
+ glsafe(::glDisable(GL_MULTISAMPLE));
+
+ glsafe(::glDisable(GL_BLEND));
+ glsafe(::glEnable(GL_DEPTH_TEST));
+
+ glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
+
+ _render_volumes_for_picking();
+
+ if (m_multisample_allowed)
+ glsafe(::glEnable(GL_MULTISAMPLE));
+
+ int width = std::max((int)m_rectangle_selection.get_width(), 1);
+ int height = std::max((int)m_rectangle_selection.get_height(), 1);
+ int px_count = width * height;
+
+ int left = (int)m_rectangle_selection.get_left();
+ int top = get_canvas_size().get_height() - (int)m_rectangle_selection.get_top();
+ if ((left >= 0) && (top >= 0))
+ {
+#define USE_PARALLEL 1
+#if USE_PARALLEL
+ struct Pixel
+ {
+ std::array data;
+ int id() const { return data[0] + (data[1] << 8) + (data[2] << 16); }
+ };
+
+ std::vector frame(px_count);
+ glsafe(::glReadPixels(left, top, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (void*)frame.data()));
+
+ tbb::spin_mutex mutex;
+ tbb::parallel_for(tbb::blocked_range(0, frame.size(), (size_t)width),
+ [this, &frame, &idxs, &mutex](const tbb::blocked_range& range) {
+ for (size_t i = range.begin(); i < range.end(); ++i)
+ {
+ int volume_id = frame[i].id();
+ if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size()))
+ {
+ mutex.lock();
+ idxs.insert(volume_id);
+ mutex.unlock();
+ }
+ }
+ }
+ );
+#else
+ std::vector frame(4 * px_count);
+ glsafe(::glReadPixels(left, top, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (void*)frame.data()));
+
+ for (int i = 0; i < px_count; ++i)
+ {
+ int px_id = 4 * i;
+ int volume_id = frame[px_id] + (frame[px_id + 1] << 8) + (frame[px_id + 2] << 16);
+ if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size()))
+ idxs.insert(volume_id);
+ }
+#endif // USE_PARALLEL
+ }
+ }
+
+ m_hover_volume_idxs.assign(idxs.begin(), idxs.end());
+ _update_volumes_hover_state();
+}
+
void GLCanvas3D::_render_background() const
{
glsafe(::glPushMatrix());
@@ -4123,24 +4311,93 @@ void GLCanvas3D::_update_volumes_hover_state() const
{
for (GLVolume* v : m_volumes.volumes)
{
- v->hover = false;
+ v->hover = GLVolume::HS_None;
}
- if (m_hover_volume_id == -1)
+ if (m_hover_volume_idxs.empty())
return;
- GLVolume* volume = m_volumes.volumes[m_hover_volume_id];
- if (volume->is_modifier)
- volume->hover = true;
- else
- {
- int object_idx = volume->object_idx();
- int instance_idx = volume->instance_idx();
+ bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); // additive select/deselect
+ bool shift_pressed = wxGetKeyState(WXK_SHIFT); // select by rectangle
+ bool alt_pressed = wxGetKeyState(WXK_ALT); // deselect by rectangle
- for (GLVolume* v : m_volumes.volumes)
+ if (alt_pressed && (shift_pressed || ctrl_pressed))
+ {
+ // illegal combinations of keys
+ m_hover_volume_idxs.clear();
+ return;
+ }
+
+ bool selection_modifiers_only = m_selection.is_empty() || m_selection.is_any_modifier();
+
+ bool hover_modifiers_only = true;
+ for (int i : m_hover_volume_idxs)
+ {
+ if (!m_volumes.volumes[i]->is_modifier)
{
- if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx))
- v->hover = true;
+ hover_modifiers_only = false;
+ break;
+ }
+ }
+
+ std::set> hover_instances;
+ for (int i : m_hover_volume_idxs)
+ {
+ const GLVolume& v = *m_volumes.volumes[i];
+ hover_instances.insert(std::make_pair(v.object_idx(), v.instance_idx()));
+ }
+
+ bool hover_from_single_instance = hover_instances.size() == 1;
+
+ if (hover_modifiers_only && !hover_from_single_instance)
+ {
+ // do not allow to select volumes from different instances
+ m_hover_volume_idxs.clear();
+ return;
+ }
+
+ for (int i : m_hover_volume_idxs)
+ {
+ GLVolume& volume = *m_volumes.volumes[i];
+ if (volume.hover != GLVolume::HS_None)
+ continue;
+
+ bool deselect = volume.selected && ((ctrl_pressed && !shift_pressed) || alt_pressed);
+ // (volume->is_modifier && !selection_modifiers_only && !is_ctrl_pressed) -> allows hovering on selected modifiers belonging to selection of type Instance
+ bool select = (!volume.selected || (volume.is_modifier && !selection_modifiers_only && !ctrl_pressed)) && !alt_pressed;
+
+ if (select || deselect)
+ {
+ bool as_volume =
+ volume.is_modifier && hover_from_single_instance && !ctrl_pressed &&
+ (
+ (!deselect) ||
+ (deselect && !m_selection.is_single_full_instance() && (volume.object_idx() == m_selection.get_object_idx()) && (volume.instance_idx() == m_selection.get_instance_idx()))
+ );
+
+ if (as_volume)
+ {
+ if (deselect)
+ volume.hover = GLVolume::HS_Deselect;
+ else
+ volume.hover = GLVolume::HS_Select;
+ }
+ else
+ {
+ int object_idx = volume.object_idx();
+ int instance_idx = volume.instance_idx();
+
+ for (GLVolume* v : m_volumes.volumes)
+ {
+ if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx))
+ {
+ if (deselect)
+ v->hover = GLVolume::HS_Deselect;
+ else
+ v->hover = GLVolume::HS_Select;
+ }
+ }
+ }
}
}
}
@@ -5419,6 +5676,55 @@ void GLCanvas3D::_resize_toolbars() const
}
#endif // !ENABLE_SVG_ICONS
+void GLCanvas3D::_update_selection_from_hover()
+{
+ bool ctrl_pressed = wxGetKeyState(WXK_CONTROL);
+
+ if (m_hover_volume_idxs.empty())
+ {
+ if (!ctrl_pressed && (m_rectangle_selection.get_state() == GLSelectionRectangle::Select))
+ m_selection.clear();
+
+ return;
+ }
+
+ GLSelectionRectangle::EState state = m_rectangle_selection.get_state();
+
+ bool hover_modifiers_only = true;
+ for (int i : m_hover_volume_idxs)
+ {
+ if (!m_volumes.volumes[i]->is_modifier)
+ {
+ hover_modifiers_only = false;
+ break;
+ }
+ }
+
+ if ((state == GLSelectionRectangle::Select) && !ctrl_pressed)
+ m_selection.clear();
+
+ for (int i : m_hover_volume_idxs)
+ {
+ if (state == GLSelectionRectangle::Select)
+ {
+ if (hover_modifiers_only)
+ {
+ const GLVolume& v = *m_volumes.volumes[i];
+ m_selection.add_volume(v.object_idx(), v.volume_idx(), v.instance_idx(), false);
+ }
+ else
+ m_selection.add(i, false);
+ }
+ else
+ m_selection.remove(i);
+ }
+
+ m_gizmos.refresh_on_off_state(m_selection);
+ m_gizmos.update_data(*this);
+ post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
+ m_dirty = true;
+}
+
const Print* GLCanvas3D::fff_print() const
{
return (m_process == nullptr) ? nullptr : m_process->fff_print();
diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp
index 8abc0378e..4670b7221 100644
--- a/src/slic3r/GUI/GLCanvas3D.hpp
+++ b/src/slic3r/GUI/GLCanvas3D.hpp
@@ -303,6 +303,7 @@ class GLCanvas3D
Vec2d position;
Vec3d scene_position;
Drag drag;
+ bool ignore_left_up;
Mouse();
@@ -319,7 +320,6 @@ class GLCanvas3D
}
};
-private:
struct SlaCap
{
struct Triangles
@@ -399,6 +399,23 @@ private:
void render(const GLCanvas3D& canvas) const;
};
+#if ENABLE_RENDER_STATISTICS
+ struct RenderStats
+ {
+ long long last_frame;
+
+ RenderStats() : last_frame(0) {}
+ };
+#endif // ENABLE_RENDER_STATISTICS
+
+public:
+ enum ECursorType : unsigned char
+ {
+ Standard,
+ Cross
+ };
+
+private:
wxGLCanvas* m_canvas;
wxGLContext* m_context;
#if ENABLE_RETINA_GL
@@ -433,7 +450,7 @@ private:
bool m_initialized;
bool m_use_VBOs;
bool m_apply_zoom_to_volumes_filter;
- mutable int m_hover_volume_id;
+ mutable std::vector m_hover_volume_idxs;
bool m_warning_texture_enabled;
bool m_legend_texture_enabled;
bool m_picking_enabled;
@@ -443,6 +460,8 @@ private:
bool m_regenerate_volumes;
bool m_moving;
bool m_tab_down;
+ ECursorType m_cursor_type;
+ GLSelectionRectangle m_rectangle_selection;
// Following variable is obsolete and it should be safe to remove it.
// I just don't want to do it now before a release (Lukas Matena 24.3.2019)
@@ -454,6 +473,10 @@ private:
GCodePreviewVolumeIndex m_gcode_preview_volume_index;
+#if ENABLE_RENDER_STATISTICS
+ RenderStats m_render_stats;
+#endif // ENABLE_RENDER_STATISTICS
+
public:
GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar);
~GLCanvas3D();
@@ -582,7 +605,7 @@ public:
float get_view_toolbar_height() const { return m_view_toolbar.get_height(); }
int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; }
- int get_hover_volume_id() const { return m_hover_volume_id; }
+ int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); }
// Returns the view ray line, in world coordinate, at the given mouse position.
Linef3 mouse_ray(const Point& mouse_pos);
@@ -594,6 +617,7 @@ public:
double get_size_proportional_to_max_bed_size(double factor) const;
+ void set_cursor(ECursorType type);
void msw_rescale();
private:
@@ -612,6 +636,7 @@ private:
void _refresh_if_shown_on_screen();
void _picking_pass() const;
+ void _rectangular_selection_picking_pass() const;
void _render_background() const;
void _render_bed(float theta) const;
void _render_axes() const;
@@ -690,6 +715,9 @@ private:
void _resize_toolbars() const;
#endif // !ENABLE_SVG_ICONS
+ // updates the selection from the content of m_hover_volume_idxs
+ void _update_selection_from_hover();
+
static std::vector _parse_colors(const std::vector& colors);
public:
diff --git a/src/slic3r/GUI/GLSelectionRectangle.cpp b/src/slic3r/GUI/GLSelectionRectangle.cpp
new file mode 100644
index 000000000..9684bb5ec
--- /dev/null
+++ b/src/slic3r/GUI/GLSelectionRectangle.cpp
@@ -0,0 +1,117 @@
+#include "GLSelectionRectangle.hpp"
+#include "Camera.hpp"
+#include "3DScene.hpp"
+#include "GLCanvas3D.hpp"
+
+#include
+
+namespace Slic3r {
+namespace GUI {
+
+ void GLSelectionRectangle::start_dragging(const Vec2d& mouse_position, EState state)
+ {
+ if (is_dragging() || (state == Off))
+ return;
+
+ m_state = state;
+ m_start_corner = mouse_position;
+ m_end_corner = mouse_position;
+ }
+
+ void GLSelectionRectangle::dragging(const Vec2d& mouse_position)
+ {
+ if (!is_dragging())
+ return;
+
+ m_end_corner = mouse_position;
+ }
+
+ std::vector GLSelectionRectangle::stop_dragging(const GLCanvas3D& canvas, const std::vector& points)
+ {
+ std::vector out;
+
+ if (!is_dragging())
+ return out;
+
+ m_state = Off;
+
+ const Camera& camera = canvas.get_camera();
+ const std::array& viewport = camera.get_viewport();
+ const Transform3d& modelview_matrix = camera.get_view_matrix();
+ const Transform3d& projection_matrix = camera.get_projection_matrix();
+
+ // bounding box created from the rectangle corners - will take care of order of the corners
+ BoundingBox rectangle(Points{ Point(m_start_corner.cast()), Point(m_end_corner.cast()) });
+
+ // Iterate over all points and determine whether they're in the rectangle.
+ for (unsigned int i = 0; i stop_dragging(const GLCanvas3D& canvas, const std::vector& points);
+
+ // Disables the rectangle.
+ void stop_dragging();
+
+ void render(const GLCanvas3D& canvas) const;
+
+ bool is_dragging() const { return m_state != Off; }
+ EState get_state() const { return m_state; }
+
+ float get_width() const { return std::abs(m_start_corner(0) - m_end_corner(0)); }
+ float get_height() const { return std::abs(m_start_corner(1) - m_end_corner(1)); }
+ float get_left() const { return std::min(m_start_corner(0), m_end_corner(0)); }
+ float get_right() const { return std::max(m_start_corner(0), m_end_corner(0)); }
+ float get_top() const { return std::max(m_start_corner(1), m_end_corner(1)); }
+ float get_bottom() const { return std::min(m_start_corner(1), m_end_corner(1)); }
+
+private:
+ EState m_state = Off;
+ Vec2d m_start_corner;
+ Vec2d m_end_corner;
+};
+
+
+} // namespace GUI
+} // namespace Slic3r
+
+
+#endif // slic3r_GLGizmoSlaSupports_hpp_
diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp
index 4625f572f..ece46f05a 100644
--- a/src/slic3r/GUI/GUI_ObjectList.cpp
+++ b/src/slic3r/GUI/GUI_ObjectList.cpp
@@ -1165,7 +1165,7 @@ void ObjectList::append_menu_item_fix_through_netfabb(wxMenu* menu)
void ObjectList::append_menu_item_export_stl(wxMenu* menu) const
{
append_menu_item(menu, wxID_ANY, _(L("Export as STL")) + dots, "",
- [](wxCommandEvent&) { wxGetApp().plater()->export_stl(true); }, "", menu);
+ [](wxCommandEvent&) { wxGetApp().plater()->export_stl(false, true); }, "", menu);
menu->AppendSeparator();
}
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
index e8fe32688..5be5e9a89 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
@@ -100,7 +100,7 @@ void GLGizmoSlaSupports::on_render(const Selection& selection) const
if (m_quadric != nullptr && selection.is_from_single_instance())
render_points(selection, false);
- render_selection_rectangle();
+ m_selection_rectangle.render(m_parent);
render_clipping_plane(selection);
glsafe(::glDisable(GL_BLEND));
@@ -240,52 +240,6 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection) const
}
-
-void GLGizmoSlaSupports::render_selection_rectangle() const
-{
- if (m_selection_rectangle_status == srOff)
- return;
-
- glsafe(::glLineWidth(1.5f));
- float render_color[3] = {0.f, 1.f, 0.f};
- if (m_selection_rectangle_status == srDeselect) {
- render_color[0] = 1.f;
- render_color[1] = 0.3f;
- render_color[2] = 0.3f;
- }
- glsafe(::glColor3fv(render_color));
-
- glsafe(::glPushAttrib(GL_TRANSFORM_BIT)); // remember current MatrixMode
-
- glsafe(::glMatrixMode(GL_MODELVIEW)); // cache modelview matrix and set to identity
- glsafe(::glPushMatrix());
- glsafe(::glLoadIdentity());
-
- glsafe(::glMatrixMode(GL_PROJECTION)); // cache projection matrix and set to identity
- glsafe(::glPushMatrix());
- glsafe(::glLoadIdentity());
-
- glsafe(::glOrtho(0.f, m_canvas_width, m_canvas_height, 0.f, -1.f, 1.f)); // set projection matrix so that world coords = window coords
-
- // render the selection rectangle (window coordinates):
- glsafe(::glPushAttrib(GL_ENABLE_BIT));
- glsafe(::glLineStipple(4, 0xAAAA));
- glsafe(::glEnable(GL_LINE_STIPPLE));
-
- ::glBegin(GL_LINE_LOOP);
- ::glVertex3f((GLfloat)m_selection_rectangle_start_corner(0), (GLfloat)m_selection_rectangle_start_corner(1), (GLfloat)0.5f);
- ::glVertex3f((GLfloat)m_selection_rectangle_end_corner(0), (GLfloat)m_selection_rectangle_start_corner(1), (GLfloat)0.5f);
- ::glVertex3f((GLfloat)m_selection_rectangle_end_corner(0), (GLfloat)m_selection_rectangle_end_corner(1), (GLfloat)0.5f);
- ::glVertex3f((GLfloat)m_selection_rectangle_start_corner(0), (GLfloat)m_selection_rectangle_end_corner(1), (GLfloat)0.5f);
- glsafe(::glEnd());
- glsafe(::glPopAttrib());
-
- glsafe(::glPopMatrix()); // restore former projection matrix
- glsafe(::glMatrixMode(GL_MODELVIEW));
- glsafe(::glPopMatrix()); // restore former modelview matrix
- glsafe(::glPopAttrib()); // restore former MatrixMode
-}
-
void GLGizmoSlaSupports::on_render_for_picking(const Selection& selection) const
{
glsafe(::glEnable(GL_DEPTH_TEST));
@@ -513,11 +467,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) {
if (m_hover_id == -1) {
if (shift_down || alt_down) {
- m_selection_rectangle_status = shift_down ? srSelect : srDeselect;
- m_selection_rectangle_start_corner = mouse_position;
- m_selection_rectangle_end_corner = mouse_position;
- m_canvas_width = m_parent.get_canvas_size().get_width();
- m_canvas_height = m_parent.get_canvas_size().get_height();
+ m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect);
}
}
else {
@@ -533,7 +483,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
}
// left down without selection rectangle - place point on the mesh:
- if (action == SLAGizmoEventType::LeftDown && m_selection_rectangle_status == srOff && !shift_down) {
+ if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle.is_dragging() && !shift_down) {
// If any point is in hover state, this should initiate its move - return control back to GLCanvas:
if (m_hover_id != -1)
return false;
@@ -558,38 +508,36 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
}
// left up with selection rectangle - select points inside the rectangle:
- if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle_status != srOff) {
- const Transform3d& instance_matrix = m_model_object->instances[m_active_instance]->get_transformation().get_matrix();
- const Camera& camera = m_parent.get_camera();
- const std::array& viewport = camera.get_viewport();
- const Transform3d& modelview_matrix = camera.get_view_matrix();
- const Transform3d& projection_matrix = camera.get_projection_matrix();
+ if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) {
+ // Is this a selection or deselection rectangle?
+ GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state();
+ // First collect positions of all the points in world coordinates.
+ const Transform3d& instance_matrix = m_model_object->instances[m_active_instance]->get_transformation().get_matrix();
+ std::vector points;
+ for (unsigned int i=0; i());
+ points.back()(2) += m_z_shift;
+ }
+ // Now ask the rectangle which of the points are inside.
+ const Camera& camera = m_parent.get_camera();
+ std::vector selected_idxs = m_selection_rectangle.stop_dragging(m_parent, points);
+
+ // we'll recover current look direction (in world coords) and transform it to model coords.
const Selection& selection = m_parent.get_selection();
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
-
- // bounding box created from the rectangle corners - will take care of order of the corners
- BoundingBox rectangle(Points{Point(m_selection_rectangle_start_corner.cast()), Point(m_selection_rectangle_end_corner.cast())});
-
const Transform3d& instance_matrix_no_translation_no_scaling = volume->get_instance_transformation().get_matrix(true,false,true);
-
- // we'll recover current look direction from the modelview matrix (in world coords)...
Vec3f direction_to_camera = camera.get_dir_forward().cast();
- // ...and transform it to model coords.
Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast() * direction_to_camera).normalized().eval();
Vec3f scaling = volume->get_instance_scaling_factor().cast();
direction_to_camera_mesh = Vec3f(direction_to_camera_mesh(0)*scaling(0), direction_to_camera_mesh(1)*scaling(1), direction_to_camera_mesh(2)*scaling(2));
- // Iterate over all points, check if they're in the rectangle and if so, check that they are not obscured by the mesh:
- for (unsigned int i=0; i() * support_point.pos;
- pos(2) += m_z_shift;
- GLdouble out_x, out_y, out_z;
- ::gluProject((GLdouble)pos(0), (GLdouble)pos(1), (GLdouble)pos(2), (GLdouble*)modelview_matrix.data(), (GLdouble*)projection_matrix.data(), (GLint*)viewport.data(), &out_x, &out_y, &out_z);
- out_y = m_canvas_height - out_y;
-
- if (rectangle.contains(Point(out_x, out_y)) && !is_point_clipped(support_point.pos.cast())) {
+ if (!is_point_clipped(support_point.pos.cast())) {
bool is_obscured = false;
// Cast a ray in the direction of the camera and look for intersection with the mesh:
std::vector hits;
@@ -627,14 +575,13 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
}
if (!is_obscured) {
- if (m_selection_rectangle_status == srDeselect)
+ if (rectangle_status == GLSelectionRectangle::Deselect)
unselect_point(i);
else
select_point(i);
}
}
}
- m_selection_rectangle_status = srOff;
return true;
}
@@ -652,9 +599,8 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
return true; // point has been placed and the button not released yet
// this prevents GLCanvas from starting scene rotation
- if (m_selection_rectangle_status != srOff) {
- m_selection_rectangle_end_corner = mouse_position;
- m_selection_rectangle_status = shift_down ? srSelect : srDeselect;
+ if (m_selection_rectangle.is_dragging()) {
+ m_selection_rectangle.dragging(mouse_position);
return true;
}
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp
index eaf0932c4..7e09b04ac 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp
@@ -3,6 +3,7 @@
#include "GLGizmoBase.hpp"
#include "GLGizmos.hpp"
+#include "slic3r/GUI/GLSelectionRectangle.hpp"
// There is an L function in igl that would be overridden by our localization macro - let's undefine it...
#undef L
@@ -67,13 +68,16 @@ public:
void delete_selected_points(bool force = false);
ClippingPlane get_sla_clipping_plane() const;
+ bool is_in_editing_mode() const { return m_editing_mode; }
+ bool is_selection_rectangle_dragging() const { return m_selection_rectangle.is_dragging(); }
+
private:
bool on_init();
void on_update(const UpdateData& data, const Selection& selection);
virtual void on_render(const Selection& selection) const;
virtual void on_render_for_picking(const Selection& selection) const;
- void render_selection_rectangle() const;
+ //void render_selection_rectangle() const;
void render_points(const Selection& selection, bool picking = false) const;
void render_clipping_plane(const Selection& selection) const;
bool is_mesh_update_necessary() const;
@@ -91,20 +95,12 @@ private:
mutable Vec3d m_old_clipping_plane_normal;
mutable Vec3d m_clipping_plane_normal = Vec3d::Zero();
- enum SelectionRectangleStatus {
- srOff = 0,
- srSelect = 1,
- srDeselect = 2
- }m_selection_rectangle_status = srOff;
+ GLSelectionRectangle m_selection_rectangle;
- Vec2d m_selection_rectangle_start_corner;
- Vec2d m_selection_rectangle_end_corner;
bool m_wait_for_up_event = false;
bool m_unsaved_changes = false; // Are there unsaved changes in manual mode?
bool m_selection_empty = true;
EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
- int m_canvas_width;
- int m_canvas_height;
mutable std::unique_ptr m_tms;
mutable std::unique_ptr m_supports_tms;
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
index 8e01806f1..dd4e454ae 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
@@ -696,7 +696,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas)
gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown());
processed = true;
}
- else if (evt.LeftUp() && (m_current == Flatten) && ((canvas.get_hover_volume_id() != -1) || grabber_contains_mouse()))
+ else if (evt.LeftUp() && (m_current == Flatten) && ((canvas.get_first_hover_volume_idx() != -1) || grabber_contains_mouse()))
{
// to avoid to loose the selection when user clicks an object while the Flatten gizmo is active
processed = true;
@@ -846,13 +846,34 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt, GLCanvas3D& canvas)
if (evt.GetEventType() == wxEVT_KEY_UP)
{
- if ((m_current == SlaSupports) && (keyCode == WXK_SHIFT) && gizmo_event(SLAGizmoEventType::ShiftUp))
- // shift has been just released - SLA gizmo might want to close rectangular selection.
- processed = true;
+ if (m_current == SlaSupports)
+ {
+ GLGizmoSlaSupports* gizmo = reinterpret_cast(get_current());
- if ((m_current == SlaSupports) && (keyCode == WXK_ALT) && gizmo_event(SLAGizmoEventType::AltUp))
- // alt has been just released - SLA gizmo might want to close rectangular selection.
+ if (keyCode == WXK_SHIFT)
+ {
+ // shift has been just released - SLA gizmo might want to close rectangular selection.
+ if (gizmo_event(SLAGizmoEventType::ShiftUp) || (gizmo->is_in_editing_mode() && gizmo->is_selection_rectangle_dragging()))
+ processed = true;
+ }
+ else if (keyCode == WXK_ALT)
+ {
+ // alt has been just released - SLA gizmo might want to close rectangular selection.
+ if (gizmo_event(SLAGizmoEventType::AltUp) || (gizmo->is_in_editing_mode() && gizmo->is_selection_rectangle_dragging()))
+ processed = true;
+ }
+ }
+
+// if (processed)
+// canvas.set_cursor(GLCanvas3D::Standard);
+ }
+ else if (evt.GetEventType() == wxEVT_KEY_DOWN)
+ {
+ if ((m_current == SlaSupports) && ((keyCode == WXK_SHIFT) || (keyCode == WXK_ALT)) && reinterpret_cast(get_current())->is_in_editing_mode())
+ {
+// canvas.set_cursor(GLCanvas3D::Cross);
processed = true;
+ }
}
if (processed)
diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp
index 3e3642af5..12e59c6f5 100644
--- a/src/slic3r/GUI/MainFrame.cpp
+++ b/src/slic3r/GUI/MainFrame.cpp
@@ -14,6 +14,7 @@
#include "libslic3r/Print.hpp"
#include "libslic3r/Polygon.hpp"
+#include "libslic3r/SLAPrint.hpp"
#include "Tab.hpp"
#include "PresetBundle.hpp"
@@ -205,12 +206,30 @@ void MainFrame::add_created_tab(Tab* panel)
bool MainFrame::can_save() const
{
- return (m_plater != nullptr) ? !m_plater->model().objects.empty() : false;
+ return (m_plater != nullptr) && !m_plater->model().objects.empty();
}
bool MainFrame::can_export_model() const
{
- return (m_plater != nullptr) ? !m_plater->model().objects.empty() : false;
+ return (m_plater != nullptr) && !m_plater->model().objects.empty();
+}
+
+bool MainFrame::can_export_supports() const
+{
+ if ((m_plater == nullptr) || (m_plater->printer_technology() != ptSLA) || m_plater->model().objects.empty())
+ return false;
+
+ bool can_export = false;
+ const PrintObjects& objects = m_plater->sla_print().objects();
+ for (const SLAPrintObject* object : objects)
+ {
+ if (object->has_mesh(slaposBasePool) || object->has_mesh(slaposSupportTree))
+ {
+ can_export = true;
+ break;
+ }
+ }
+ return can_export;
}
bool MainFrame::can_export_gcode() const
@@ -243,17 +262,17 @@ bool MainFrame::can_change_view() const
bool MainFrame::can_select() const
{
- return (m_plater != nullptr) ? !m_plater->model().objects.empty() : false;
+ return (m_plater != nullptr) && !m_plater->model().objects.empty();
}
bool MainFrame::can_delete() const
{
- return (m_plater != nullptr) ? !m_plater->is_selection_empty() : false;
+ return (m_plater != nullptr) && !m_plater->is_selection_empty();
}
bool MainFrame::can_delete_all() const
{
- return (m_plater != nullptr) ? !m_plater->model().objects.empty() : false;
+ return (m_plater != nullptr) && !m_plater->model().objects.empty();
}
void MainFrame::on_dpi_changed(const wxRect &suggested_rect)
@@ -298,6 +317,16 @@ void MainFrame::on_dpi_changed(const wxRect &suggested_rect)
this->Maximize(is_maximized);
}
+static std::string menu_icon(const std::string& icon_name)
+{
+#ifdef __WXMSW__
+ const std::string folder = "white\\";
+#else
+ const std::string folder = "white/";
+#endif
+ return wxGetApp().dark_mode_menus() ? folder+icon_name : icon_name;
+}
+
void MainFrame::init_menubar()
{
#ifdef __APPLE__
@@ -308,40 +337,43 @@ void MainFrame::init_menubar()
wxMenu* fileMenu = new wxMenu;
{
wxMenuItem* item_open = append_menu_item(fileMenu, wxID_ANY, _(L("&Open Project")) + dots + "\tCtrl+O", _(L("Open a project file")),
- [this](wxCommandEvent&) { if (m_plater) m_plater->load_project(); }, "open");
+ [this](wxCommandEvent&) { if (m_plater) m_plater->load_project(); }, menu_icon("open"));
wxMenuItem* item_save = append_menu_item(fileMenu, wxID_ANY, _(L("&Save Project")) + "\tCtrl+S", _(L("Save current project file")),
- [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(into_path(m_plater->get_project_filename())); }, "save");
+ [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(into_path(m_plater->get_project_filename())); }, menu_icon("save"));
wxMenuItem* item_save_as = append_menu_item(fileMenu, wxID_ANY, _(L("Save Project &as")) + dots + "\tCtrl+Alt+S", _(L("Save current project file as")),
- [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(); }, "save");
+ [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(); }, menu_icon("save"));
fileMenu->AppendSeparator();
wxMenu* import_menu = new wxMenu();
wxMenuItem* item_import_model = append_menu_item(import_menu, wxID_ANY, _(L("Import STL/OBJ/AM&F/3MF")) + dots + "\tCtrl+I", _(L("Load a model")),
- [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, "import_plater");
+ [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, menu_icon("import_plater"));
import_menu->AppendSeparator();
append_menu_item(import_menu, wxID_ANY, _(L("Import &Config")) + dots + "\tCtrl+L", _(L("Load exported configuration file")),
- [this](wxCommandEvent&) { load_config_file(); }, "import_config");
+ [this](wxCommandEvent&) { load_config_file(); }, menu_icon("import_config"));
append_menu_item(import_menu, wxID_ANY, _(L("Import Config from &project")) + dots +"\tCtrl+Alt+L", _(L("Load configuration from project file")),
- [this](wxCommandEvent&) { if (m_plater) m_plater->extract_config_from_project(); }, "import_config");
+ [this](wxCommandEvent&) { if (m_plater) m_plater->extract_config_from_project(); }, menu_icon("import_config"));
import_menu->AppendSeparator();
append_menu_item(import_menu, wxID_ANY, _(L("Import Config &Bundle")) + dots, _(L("Load presets from a bundle")),
- [this](wxCommandEvent&) { load_configbundle(); }, "import_config_bundle");
+ [this](wxCommandEvent&) { load_configbundle(); }, menu_icon("import_config_bundle"));
append_submenu(fileMenu, import_menu, wxID_ANY, _(L("&Import")), "");
wxMenu* export_menu = new wxMenu();
wxMenuItem* item_export_gcode = append_menu_item(export_menu, wxID_ANY, _(L("Export &G-code")) + dots +"\tCtrl+G", _(L("Export current plate as G-code")),
- [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(); }, "export_gcode");
+ [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(); }, menu_icon("export_gcode"));
+ m_changeable_menu_items.push_back(item_export_gcode);
export_menu->AppendSeparator();
wxMenuItem* item_export_stl = append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &STL")) + dots, _(L("Export current plate as STL")),
- [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(); }, "export_plater");
+ [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(); }, menu_icon("export_plater"));
+ wxMenuItem* item_export_stl_sla = append_menu_item(export_menu, wxID_ANY, _(L("Export plate as STL including supports")) + dots, _(L("Export current plate as STL including supports")),
+ [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(true); }, menu_icon("export_plater"));
wxMenuItem* item_export_amf = append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &AMF")) + dots, _(L("Export current plate as AMF")),
- [this](wxCommandEvent&) { if (m_plater) m_plater->export_amf(); }, "export_plater");
+ [this](wxCommandEvent&) { if (m_plater) m_plater->export_amf(); }, menu_icon("export_plater"));
export_menu->AppendSeparator();
append_menu_item(export_menu, wxID_ANY, _(L("Export &Config")) +dots +"\tCtrl+E", _(L("Export current configuration to file")),
- [this](wxCommandEvent&) { export_config(); }, "export_config");
+ [this](wxCommandEvent&) { export_config(); }, menu_icon("export_config"));
append_menu_item(export_menu, wxID_ANY, _(L("Export Config &Bundle")) + dots, _(L("Export all presets to file")),
- [this](wxCommandEvent&) { export_configbundle(); }, "export_config_bundle");
+ [this](wxCommandEvent&) { export_configbundle(); }, menu_icon("export_config_bundle"));
append_submenu(fileMenu, export_menu, wxID_ANY, _(L("&Export")), "");
fileMenu->AppendSeparator();
@@ -369,22 +401,23 @@ void MainFrame::init_menubar()
fileMenu->AppendSeparator();
#endif
m_menu_item_reslice_now = append_menu_item(fileMenu, wxID_ANY, _(L("(Re)Slice &Now")) + "\tCtrl+R", _(L("Start new slicing process")),
- [this](wxCommandEvent&) { reslice_now(); }, "re_slice");
+ [this](wxCommandEvent&) { reslice_now(); }, menu_icon("re_slice"));
fileMenu->AppendSeparator();
append_menu_item(fileMenu, wxID_ANY, _(L("&Repair STL file")) + dots, _(L("Automatically repair an STL file")),
- [this](wxCommandEvent&) { repair_stl(); }, "wrench");
+ [this](wxCommandEvent&) { repair_stl(); }, menu_icon("wrench"));
fileMenu->AppendSeparator();
append_menu_item(fileMenu, wxID_EXIT, _(L("&Quit")), wxString::Format(_(L("Quit %s")), SLIC3R_APP_NAME),
[this](wxCommandEvent&) { Close(false); });
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_plater != nullptr); }, item_open->GetId());
- Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable((m_plater != nullptr) && can_save()); }, item_save->GetId());
- Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable((m_plater != nullptr) && can_save()); }, item_save_as->GetId());
+ Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_save()); }, item_save->GetId());
+ Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_save()); }, item_save_as->GetId());
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_plater != nullptr); }, item_import_model->GetId());
- Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable((m_plater != nullptr) && can_export_gcode()); }, item_export_gcode->GetId());
- Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable((m_plater != nullptr) && can_export_model()); }, item_export_stl->GetId());
- Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable((m_plater != nullptr) && can_export_model()); }, item_export_amf->GetId());
- Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable((m_plater != nullptr) && can_slice()); }, m_menu_item_reslice_now->GetId());
+ Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_export_gcode()); }, item_export_gcode->GetId());
+ Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_export_model()); }, item_export_stl->GetId());
+ Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_export_supports()); }, item_export_stl_sla->GetId());
+ Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_export_model()); }, item_export_amf->GetId());
+ Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_slice()); }, m_menu_item_reslice_now->GetId());
}
#ifdef _MSC_VER
@@ -409,19 +442,19 @@ void MainFrame::init_menubar()
wxString hotkey_delete = "Del";
#endif
wxMenuItem* item_select_all = append_menu_item(editMenu, wxID_ANY, _(L("&Select all")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "A", _(L("Selects all objects")),
- [this](wxCommandEvent&) { m_plater->select_all(); }, "");
+ [this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->select_all(); }, "");
editMenu->AppendSeparator();
wxMenuItem* item_delete_sel = append_menu_item(editMenu, wxID_ANY, _(L("&Delete selected")) + sep + hotkey_delete, _(L("Deletes the current selection")),
- [this](wxCommandEvent&) { m_plater->remove_selected(); }, "remove_menu");
+ [this](wxCommandEvent&) { m_plater->remove_selected(); }, menu_icon("remove_menu"));
wxMenuItem* item_delete_all = append_menu_item(editMenu, wxID_ANY, _(L("Delete &all")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + hotkey_delete, _(L("Deletes all objects")),
- [this](wxCommandEvent&) { m_plater->reset(); }, "delete_all_menu");
+ [this](wxCommandEvent&) { m_plater->reset_with_confirm(); }, menu_icon("delete_all_menu"));
editMenu->AppendSeparator();
wxMenuItem* item_copy = append_menu_item(editMenu, wxID_ANY, _(L("&Copy")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "C", _(L("Copy selection to clipboard")),
- [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); }, "copy_menu");
+ [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); }, menu_icon("copy_menu"));
wxMenuItem* item_paste = append_menu_item(editMenu, wxID_ANY, _(L("&Paste")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "V", _(L("Paste clipboard")),
- [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); }, "paste_menu");
+ [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); }, menu_icon("paste_menu"));
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_select()); }, item_select_all->GetId());
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete()); }, item_delete_sel->GetId());
@@ -436,24 +469,25 @@ void MainFrame::init_menubar()
size_t tab_offset = 0;
if (m_plater) {
append_menu_item(windowMenu, wxID_HIGHEST + 1, _(L("&Plater Tab")) + "\tCtrl+1", _(L("Show the plater")),
- [this](wxCommandEvent&) { select_tab(0); }, "plater");
+ [this](wxCommandEvent&) { select_tab(0); }, menu_icon("plater"));
tab_offset += 1;
}
if (tab_offset > 0) {
windowMenu->AppendSeparator();
}
append_menu_item(windowMenu, wxID_HIGHEST + 2, _(L("P&rint Settings Tab")) + "\tCtrl+2", _(L("Show the print settings")),
- [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 0); }, "cog");
- append_menu_item(windowMenu, wxID_HIGHEST + 3, _(L("&Filament Settings Tab")) + "\tCtrl+3", _(L("Show the filament settings")),
- [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 1); }, "spool.png");
+ [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 0); }, menu_icon("cog"));
+ wxMenuItem* item_material_tab = append_menu_item(windowMenu, wxID_HIGHEST + 3, _(L("&Filament Settings Tab")) + "\tCtrl+3", _(L("Show the filament settings")),
+ [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 1); }, menu_icon("spool"));
+ m_changeable_menu_items.push_back(item_material_tab);
append_menu_item(windowMenu, wxID_HIGHEST + 4, _(L("Print&er Settings Tab")) + "\tCtrl+4", _(L("Show the printer settings")),
- [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 2); }, "printer");
+ [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 2); }, menu_icon("printer"));
if (m_plater) {
windowMenu->AppendSeparator();
wxMenuItem* item_3d = append_menu_item(windowMenu, wxID_HIGHEST + 5, _(L("3&D")) + "\tCtrl+5", _(L("Show the 3D editing view")),
- [this](wxCommandEvent&) { m_plater->select_view_3D("3D"); }, "editor_menu");
+ [this](wxCommandEvent&) { m_plater->select_view_3D("3D"); }, menu_icon("editor_menu"));
wxMenuItem* item_preview = append_menu_item(windowMenu, wxID_HIGHEST + 6, _(L("Pre&view")) + "\tCtrl+6", _(L("Show the 3D slices preview")),
- [this](wxCommandEvent&) { m_plater->select_view_3D("Preview"); }, "preview_menu");
+ [this](wxCommandEvent&) { m_plater->select_view_3D("Preview"); }, menu_icon("preview_menu"));
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_3d->GetId());
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_preview->GetId());
@@ -474,7 +508,7 @@ void MainFrame::init_menubar()
windowMenu->AppendSeparator();
append_menu_item(windowMenu, wxID_ANY, _(L("Print &Host Upload Queue")) + "\tCtrl+J", _(L("Display the Print Host Upload Queue window")),
- [this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "upload_queue");
+ [this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, menu_icon("upload_queue"));
}
// View menu
@@ -554,6 +588,19 @@ void MainFrame::init_menubar()
}, wxID_EXIT);
}
#endif
+
+ if (plater()->printer_technology() == ptSLA)
+ update_menubar();
+}
+
+void MainFrame::update_menubar()
+{
+ const bool is_fff = plater()->printer_technology() == ptFFF;
+
+ m_changeable_menu_items[miExport] ->SetItemLabel((is_fff ? _(L("Export &G-code")) : _(L("Export")) ) + dots + "\tCtrl+G");
+
+ m_changeable_menu_items[miMaterialTab] ->SetItemLabel((is_fff ? _(L("&Filament Settings Tab")) : _(L("Mate&rial Settings Tab"))) + "\tCtrl+3");
+ m_changeable_menu_items[miMaterialTab] ->SetBitmap(create_scaled_bitmap(this, menu_icon(is_fff ? "spool": "resin")));
}
// To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG".
diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp
index a8b2be2bc..f3d582681 100644
--- a/src/slic3r/GUI/MainFrame.hpp
+++ b/src/slic3r/GUI/MainFrame.hpp
@@ -63,6 +63,7 @@ class MainFrame : public DPIFrame
bool can_save() const;
bool can_export_model() const;
+ bool can_export_supports() const;
bool can_export_gcode() const;
bool can_slice() const;
bool can_change_view() const;
@@ -70,6 +71,16 @@ class MainFrame : public DPIFrame
bool can_delete() const;
bool can_delete_all() const;
+ // MenuBar items changeable in respect to printer technology
+ enum MenuItems
+ { // FFF SLA
+ miExport = 0, // Export G-code Export
+ miMaterialTab, // Filament Settings Material Settings
+ };
+
+ // vector of a MenuBar items changeable in respect to printer technology
+ std::vector m_changeable_menu_items;
+
protected:
virtual void on_dpi_changed(const wxRect &suggested_rect);
@@ -83,6 +94,7 @@ public:
void create_preset_tabs();
void add_created_tab(Tab* panel);
void init_menubar();
+ void update_menubar();
void update_ui_from_settings();
bool is_loaded() const { return m_loaded; }
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 4280e4dbe..b3757dc8b 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -1447,7 +1447,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
// 3DScene/Toolbar:
view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this);
view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); });
- view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [this](SimpleEvent&) { reset(); });
+ view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { arrange(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); });
@@ -2925,7 +2925,7 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/
[this](wxCommandEvent&) { reload_from_disk(); });
append_menu_item(menu, wxID_ANY, _(L("Export as STL")) + dots, _(L("Export the selected object as STL file")),
- [this](wxCommandEvent&) { q->export_stl(true); });
+ [this](wxCommandEvent&) { q->export_stl(false, true); });
menu->AppendSeparator();
}
@@ -3283,6 +3283,11 @@ void Plater::select_all() { p->select_all(); }
void Plater::remove(size_t obj_idx) { p->remove(obj_idx); }
void Plater::reset() { p->reset(); }
+void Plater::reset_with_confirm()
+{
+ if (wxMessageDialog((wxWindow*)this, _(L("All objects will be removed, continue ?")), _(L("Delete all")), wxYES_NO | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES)
+ reset();
+}
void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_model(obj_idx); }
@@ -3432,7 +3437,7 @@ void Plater::export_gcode()
p->export_gcode(std::move(output_path), PrintHostJob());
}
-void Plater::export_stl(bool selection_only)
+void Plater::export_stl(bool extended, bool selection_only)
{
if (p->model.objects.empty()) { return; }
@@ -3467,8 +3472,65 @@ void Plater::export_stl(bool selection_only)
}
}
else
+ {
mesh = p->model.mesh();
+ if (extended && (p->printer_technology == ptSLA))
+ {
+ const PrintObjects& objects = p->sla_print.objects();
+ for (const SLAPrintObject* object : objects)
+ {
+ const ModelObject* model_object = object->model_object();
+ Transform3d mesh_trafo_inv = object->trafo().inverse();
+ bool is_left_handed = object->is_left_handed();
+
+ TriangleMesh pad_mesh;
+ bool has_pad_mesh = object->has_mesh(slaposBasePool);
+ if (has_pad_mesh)
+ {
+ pad_mesh = object->get_mesh(slaposBasePool);
+ pad_mesh.transform(mesh_trafo_inv);
+ }
+
+ TriangleMesh supports_mesh;
+ bool has_supports_mesh = object->has_mesh(slaposSupportTree);
+ if (has_supports_mesh)
+ {
+ supports_mesh = object->get_mesh(slaposSupportTree);
+ supports_mesh.transform(mesh_trafo_inv);
+ }
+
+ const std::vector& obj_instances = object->instances();
+ for (const SLAPrintObject::Instance& obj_instance : obj_instances)
+ {
+ auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(),
+ [&obj_instance](const ModelInstance *mi) { return mi->id() == obj_instance.instance_id; });
+ assert(it != model_object->instances.end());
+
+ if (it != model_object->instances.end())
+ {
+ int instance_idx = it - model_object->instances.begin();
+ const Transform3d& inst_transform = object->model_object()->instances[instance_idx]->get_transformation().get_matrix();
+
+ if (has_pad_mesh)
+ {
+ TriangleMesh inst_pad_mesh = pad_mesh;
+ inst_pad_mesh.transform(inst_transform, is_left_handed);
+ mesh.merge(inst_pad_mesh);
+ }
+
+ if (has_supports_mesh)
+ {
+ TriangleMesh inst_supports_mesh = supports_mesh;
+ inst_supports_mesh.transform(inst_transform, is_left_handed);
+ mesh.merge(inst_supports_mesh);
+ }
+ }
+ }
+ }
+ }
+ }
+
Slic3r::store_stl(path_u8.c_str(), &mesh, true);
p->statusbar()->set_status_text(wxString::Format(_(L("STL file exported to %s")), path));
}
@@ -3748,6 +3810,9 @@ void Plater::set_printer_technology(PrinterTechnology printer_technology)
p->label_btn_export = printer_technology == ptFFF ? L("Export G-code") : L("Export");
p->label_btn_send = printer_technology == ptFFF ? L("Send G-code") : L("Send to printer");
+
+ if (wxGetApp().mainframe)
+ wxGetApp().mainframe->update_menubar();
}
void Plater::changed_object(int obj_idx)
diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp
index ad5a45adb..e4327f433 100644
--- a/src/slic3r/GUI/Plater.hpp
+++ b/src/slic3r/GUI/Plater.hpp
@@ -153,6 +153,7 @@ public:
void select_all();
void remove(size_t obj_idx);
void reset();
+ void reset_with_confirm();
void delete_object_from_model(size_t obj_idx);
void remove_selected();
void increase_instances(size_t num = 1);
@@ -163,7 +164,7 @@ public:
void cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false);
void export_gcode();
- void export_stl(bool selection_only = false);
+ void export_stl(bool extended = false, bool selection_only = false);
void export_amf();
void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path());
void reslice();