diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e6cf45bd4..8adea28c34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,10 @@ foreach (_cache_var ${_cache_vars}) endif () endforeach() +if (SLIC3R_GUI) + add_definitions(-DSLIC3R_GUI) +endif () + if (MSVC) if (SLIC3R_MSVC_COMPILE_PARALLEL) add_compile_options(/MP) diff --git a/resources/icons/add_copies.svg b/resources/icons/add_copies.svg new file mode 100644 index 0000000000..45b1d27cf9 --- /dev/null +++ b/resources/icons/add_copies.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + diff --git a/resources/icons/add_modifier.svg b/resources/icons/add_modifier.svg new file mode 100644 index 0000000000..c3cfaabbb2 --- /dev/null +++ b/resources/icons/add_modifier.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/resources/icons/add_part.svg b/resources/icons/add_part.svg new file mode 100644 index 0000000000..5f0afdcc35 --- /dev/null +++ b/resources/icons/add_part.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/resources/icons/advanced_plus.svg b/resources/icons/advanced_plus.svg new file mode 100644 index 0000000000..48c4e08db0 --- /dev/null +++ b/resources/icons/advanced_plus.svg @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/resources/icons/browse.svg b/resources/icons/browse.svg new file mode 100644 index 0000000000..c4297c41da --- /dev/null +++ b/resources/icons/browse.svg @@ -0,0 +1,11 @@ + + + + + + + diff --git a/resources/icons/copy.svg b/resources/icons/copy.svg new file mode 100644 index 0000000000..9b8430dd79 --- /dev/null +++ b/resources/icons/copy.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/copy_menu.svg b/resources/icons/copy_menu.svg new file mode 100644 index 0000000000..0d1af6a0a7 --- /dev/null +++ b/resources/icons/copy_menu.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/delete.svg b/resources/icons/delete.svg new file mode 100644 index 0000000000..f0976a76bc --- /dev/null +++ b/resources/icons/delete.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/delete_all_menu.svg b/resources/icons/delete_all_menu.svg new file mode 100644 index 0000000000..5ee6d6ea6d --- /dev/null +++ b/resources/icons/delete_all_menu.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/dot.svg b/resources/icons/dot.svg new file mode 100644 index 0000000000..236db36786 --- /dev/null +++ b/resources/icons/dot.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/resources/icons/dot_white.svg b/resources/icons/dot_white.svg new file mode 100644 index 0000000000..90fbaf7fb1 --- /dev/null +++ b/resources/icons/dot_white.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/resources/icons/editor_menu.svg b/resources/icons/editor_menu.svg new file mode 100644 index 0000000000..223efda0f5 --- /dev/null +++ b/resources/icons/editor_menu.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + diff --git a/resources/icons/exclamation.svg b/resources/icons/exclamation.svg new file mode 100644 index 0000000000..4568026529 --- /dev/null +++ b/resources/icons/exclamation.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/resources/icons/export_config.svg b/resources/icons/export_config.svg new file mode 100644 index 0000000000..e70035dae5 --- /dev/null +++ b/resources/icons/export_config.svg @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/resources/icons/export_config_bundle.svg b/resources/icons/export_config_bundle.svg new file mode 100644 index 0000000000..1e587689d0 --- /dev/null +++ b/resources/icons/export_config_bundle.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/export_gcode.svg b/resources/icons/export_gcode.svg new file mode 100644 index 0000000000..317e01f7df --- /dev/null +++ b/resources/icons/export_gcode.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/resources/icons/export_plater.svg b/resources/icons/export_plater.svg new file mode 100644 index 0000000000..641d952ad6 --- /dev/null +++ b/resources/icons/export_plater.svg @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/resources/icons/import_config.svg b/resources/icons/import_config.svg new file mode 100644 index 0000000000..636a311510 --- /dev/null +++ b/resources/icons/import_config.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + diff --git a/resources/icons/import_config_bundle.svg b/resources/icons/import_config_bundle.svg new file mode 100644 index 0000000000..b8342760a8 --- /dev/null +++ b/resources/icons/import_config_bundle.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/import_plater.svg b/resources/icons/import_plater.svg new file mode 100644 index 0000000000..a953122f26 --- /dev/null +++ b/resources/icons/import_plater.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/resources/icons/layers.svg b/resources/icons/layers.svg index cd71fab3a3..da5dec21d5 100644 --- a/resources/icons/layers.svg +++ b/resources/icons/layers.svg @@ -5,13 +5,13 @@ - + - + - + @@ -20,7 +20,7 @@ - + diff --git a/resources/icons/mark_X.svg b/resources/icons/mark_X.svg new file mode 100644 index 0000000000..1045debc51 --- /dev/null +++ b/resources/icons/mark_X.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/resources/icons/mark_Y.svg b/resources/icons/mark_Y.svg new file mode 100644 index 0000000000..26e01b2952 --- /dev/null +++ b/resources/icons/mark_Y.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/resources/icons/mark_Z.svg b/resources/icons/mark_Z.svg new file mode 100644 index 0000000000..cd2826ac00 --- /dev/null +++ b/resources/icons/mark_Z.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/resources/icons/number_of_copies.svg b/resources/icons/number_of_copies.svg new file mode 100644 index 0000000000..a5c8affc7f --- /dev/null +++ b/resources/icons/number_of_copies.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/open.svg b/resources/icons/open.svg new file mode 100644 index 0000000000..8fcddc2a61 --- /dev/null +++ b/resources/icons/open.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/resources/icons/overlay/cut_hover.png b/resources/icons/overlay/cut_hover.png deleted file mode 100644 index b026667c28..0000000000 Binary files a/resources/icons/overlay/cut_hover.png and /dev/null differ diff --git a/resources/icons/overlay/cut_off.png b/resources/icons/overlay/cut_off.png deleted file mode 100644 index c01f205546..0000000000 Binary files a/resources/icons/overlay/cut_off.png and /dev/null differ diff --git a/resources/icons/overlay/cut_on.png b/resources/icons/overlay/cut_on.png deleted file mode 100644 index fac6ecb45d..0000000000 Binary files a/resources/icons/overlay/cut_on.png and /dev/null differ diff --git a/resources/icons/overlay/layflat_hover.png b/resources/icons/overlay/layflat_hover.png deleted file mode 100644 index 508916e480..0000000000 Binary files a/resources/icons/overlay/layflat_hover.png and /dev/null differ diff --git a/resources/icons/overlay/layflat_off.png b/resources/icons/overlay/layflat_off.png deleted file mode 100644 index 6bfadd3165..0000000000 Binary files a/resources/icons/overlay/layflat_off.png and /dev/null differ diff --git a/resources/icons/overlay/layflat_on.png b/resources/icons/overlay/layflat_on.png deleted file mode 100644 index 4892b57fc7..0000000000 Binary files a/resources/icons/overlay/layflat_on.png and /dev/null differ diff --git a/resources/icons/overlay/move_hover.png b/resources/icons/overlay/move_hover.png deleted file mode 100644 index 933c34c245..0000000000 Binary files a/resources/icons/overlay/move_hover.png and /dev/null differ diff --git a/resources/icons/overlay/move_off.png b/resources/icons/overlay/move_off.png deleted file mode 100644 index 6c921a0421..0000000000 Binary files a/resources/icons/overlay/move_off.png and /dev/null differ diff --git a/resources/icons/overlay/move_on.png b/resources/icons/overlay/move_on.png deleted file mode 100644 index 80204b52ee..0000000000 Binary files a/resources/icons/overlay/move_on.png and /dev/null differ diff --git a/resources/icons/overlay/rotate_hover.png b/resources/icons/overlay/rotate_hover.png deleted file mode 100644 index 9df377fc7f..0000000000 Binary files a/resources/icons/overlay/rotate_hover.png and /dev/null differ diff --git a/resources/icons/overlay/rotate_off.png b/resources/icons/overlay/rotate_off.png deleted file mode 100644 index f5b6529795..0000000000 Binary files a/resources/icons/overlay/rotate_off.png and /dev/null differ diff --git a/resources/icons/overlay/rotate_on.png b/resources/icons/overlay/rotate_on.png deleted file mode 100644 index 6d4c5bf31e..0000000000 Binary files a/resources/icons/overlay/rotate_on.png and /dev/null differ diff --git a/resources/icons/overlay/scale_hover.png b/resources/icons/overlay/scale_hover.png deleted file mode 100644 index 40a05fffdd..0000000000 Binary files a/resources/icons/overlay/scale_hover.png and /dev/null differ diff --git a/resources/icons/overlay/scale_off.png b/resources/icons/overlay/scale_off.png deleted file mode 100644 index 351d5a0046..0000000000 Binary files a/resources/icons/overlay/scale_off.png and /dev/null differ diff --git a/resources/icons/overlay/scale_on.png b/resources/icons/overlay/scale_on.png deleted file mode 100644 index f8450e1058..0000000000 Binary files a/resources/icons/overlay/scale_on.png and /dev/null differ diff --git a/resources/icons/overlay/sla_support_points_hover.png b/resources/icons/overlay/sla_support_points_hover.png deleted file mode 100644 index 2b385c0e74..0000000000 Binary files a/resources/icons/overlay/sla_support_points_hover.png and /dev/null differ diff --git a/resources/icons/overlay/sla_support_points_off.png b/resources/icons/overlay/sla_support_points_off.png deleted file mode 100644 index 0a1e3f570f..0000000000 Binary files a/resources/icons/overlay/sla_support_points_off.png and /dev/null differ diff --git a/resources/icons/overlay/sla_support_points_on.png b/resources/icons/overlay/sla_support_points_on.png deleted file mode 100644 index e7f5d0a875..0000000000 Binary files a/resources/icons/overlay/sla_support_points_on.png and /dev/null differ diff --git a/resources/icons/pad.svg b/resources/icons/pad.svg new file mode 100644 index 0000000000..dc5907e37f --- /dev/null +++ b/resources/icons/pad.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/paste.svg b/resources/icons/paste.svg new file mode 100644 index 0000000000..028ffb8ea0 --- /dev/null +++ b/resources/icons/paste.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/resources/icons/paste_menu.svg b/resources/icons/paste_menu.svg new file mode 100644 index 0000000000..74dbbf8eef --- /dev/null +++ b/resources/icons/paste_menu.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/resources/icons/plater.svg b/resources/icons/plater.svg new file mode 100644 index 0000000000..6fd21215b6 --- /dev/null +++ b/resources/icons/plater.svg @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/resources/icons/preview_menu.svg b/resources/icons/preview_menu.svg new file mode 100644 index 0000000000..725caf7b8a --- /dev/null +++ b/resources/icons/preview_menu.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/printer_white.svg b/resources/icons/printer_white.svg new file mode 100644 index 0000000000..d94f6fd5c4 --- /dev/null +++ b/resources/icons/printer_white.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/resources/icons/printers/PrusaResearch_SL1.png b/resources/icons/printers/PrusaResearch_SL1.png index 3c15f95eae..b4425a705f 100644 Binary files a/resources/icons/printers/PrusaResearch_SL1.png and b/resources/icons/printers/PrusaResearch_SL1.png differ diff --git a/resources/icons/re_slice.svg b/resources/icons/re_slice.svg new file mode 100644 index 0000000000..54490ea428 --- /dev/null +++ b/resources/icons/re_slice.svg @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/resources/icons/remove_copies.svg b/resources/icons/remove_copies.svg new file mode 100644 index 0000000000..d249cb5bcc --- /dev/null +++ b/resources/icons/remove_copies.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/resources/icons/remove_menu.svg b/resources/icons/remove_menu.svg new file mode 100644 index 0000000000..a25ae965b4 --- /dev/null +++ b/resources/icons/remove_menu.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + diff --git a/resources/icons/set_separate_obj.svg b/resources/icons/set_separate_obj.svg new file mode 100644 index 0000000000..c95149e2dd --- /dev/null +++ b/resources/icons/set_separate_obj.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/split_object_SMALL.svg b/resources/icons/split_object_SMALL.svg new file mode 100644 index 0000000000..7e362c2ba2 --- /dev/null +++ b/resources/icons/split_object_SMALL.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/resources/icons/split_parts_SMALL.svg b/resources/icons/split_parts_SMALL.svg new file mode 100644 index 0000000000..45f90bee06 --- /dev/null +++ b/resources/icons/split_parts_SMALL.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/resources/icons/support_blocker.svg b/resources/icons/support_blocker.svg new file mode 100644 index 0000000000..56086b2795 --- /dev/null +++ b/resources/icons/support_blocker.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/support_enforcer.svg b/resources/icons/support_enforcer.svg new file mode 100644 index 0000000000..0f0eb4907f --- /dev/null +++ b/resources/icons/support_enforcer.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + diff --git a/resources/icons/test.svg b/resources/icons/test.svg new file mode 100644 index 0000000000..abf35d0ae7 --- /dev/null +++ b/resources/icons/test.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/upload_queue.svg b/resources/icons/upload_queue.svg new file mode 100644 index 0000000000..7647178366 --- /dev/null +++ b/resources/icons/upload_queue.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + diff --git a/resources/icons/wrench.svg b/resources/icons/wrench.svg index 7966da8d8f..5266ec1ebc 100644 --- a/resources/icons/wrench.svg +++ b/resources/icons/wrench.svg @@ -3,22 +3,41 @@ - - - - - - + + + + + + + + + diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs index f6116eec70..09003f4079 100644 --- a/resources/shaders/gouraud.fs +++ b/resources/shaders/gouraud.fs @@ -2,24 +2,21 @@ const vec3 ZERO = vec3(0.0, 0.0, 0.0); +varying vec3 clipping_planes_dots; + // x = tainted, y = specular; varying vec2 intensity; varying vec3 delta_box_min; varying vec3 delta_box_max; -varying float world_z; - uniform vec4 uniform_color; -// x = min z, y = max z; -uniform vec2 z_range; void main() { - if ((world_z < z_range.x) || (z_range.y < world_z)) + if (any(lessThan(clipping_planes_dots, ZERO))) discard; - // if the fragment is outside the print volume -> use darker color vec3 color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(uniform_color.rgb, ZERO, 0.3333) : uniform_color.rgb; gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + color * intensity.x, uniform_color.a); diff --git a/resources/shaders/gouraud.vs b/resources/shaders/gouraud.vs index 84ae513913..cc54c1c449 100644 --- a/resources/shaders/gouraud.vs +++ b/resources/shaders/gouraud.vs @@ -28,13 +28,18 @@ struct PrintBoxDetection uniform PrintBoxDetection print_box; +// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane. +uniform vec2 z_range; +// Clipping plane - general orientation. Used by the SLA gizmo. +uniform vec4 clipping_plane; + // x = tainted, y = specular; varying vec2 intensity; varying vec3 delta_box_min; varying vec3 delta_box_max; -varying float world_z; +varying vec3 clipping_planes_dots; void main() { @@ -66,8 +71,11 @@ void main() { delta_box_min = ZERO; delta_box_max = ZERO; - } + } gl_Position = ftransform(); - world_z = vec3(print_box.volume_world_matrix * gl_Vertex).z; -} + // Point in homogenous coordinates. + vec4 world_pos = print_box.volume_world_matrix * gl_Vertex; + // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. + clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z); +} diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 2c436b426b..d682b24347 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -43,11 +43,21 @@ typedef Eigen::Matrix stl_normal; static_assert(sizeof(stl_vertex) == 12, "size of stl_vertex incorrect"); static_assert(sizeof(stl_normal) == 12, "size of stl_normal incorrect"); -typedef struct { +struct stl_facet { stl_normal normal; stl_vertex vertex[3]; char extra[2]; -} stl_facet; + + stl_facet rotated(const Eigen::Quaternion &rot) { + stl_facet out; + out.normal = rot * this->normal; + out.vertex[0] = rot * this->vertex[0]; + out.vertex[1] = rot * this->vertex[1]; + out.vertex[2] = rot * this->vertex[2]; + return out; + } +}; + #define SIZEOF_STL_FACET 50 static_assert(offsetof(stl_facet, normal) == 0, "stl_facet.normal has correct offset"); diff --git a/src/admesh/stlinit.cpp b/src/admesh/stlinit.cpp index e2939b8af8..911f4f5e82 100644 --- a/src/admesh/stlinit.cpp +++ b/src/admesh/stlinit.cpp @@ -41,10 +41,12 @@ stl_open(stl_file *stl, const char *file) { stl_count_facets(stl, file); stl_allocate(stl); stl_read(stl, 0, true); - if (!stl->error) fclose(stl->fp); + if (stl->fp != nullptr) { + fclose(stl->fp); + stl->fp = nullptr; + } } - void stl_initialize(stl_file *stl) { memset(stl, 0, sizeof(stl_file)); @@ -118,7 +120,7 @@ stl_count_facets(stl_file *stl, const char *file) { } /* Read the int following the header. This should contain # of facets */ - bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, stl->fp); + bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, stl->fp) != 0; #ifndef BOOST_LITTLE_ENDIAN // Convert from little endian to big endian. stl_internal_reverse_quads((char*)&header_num_facets, 4); @@ -257,7 +259,6 @@ stl_reallocate(stl_file *stl) { time running this for the stl and therefore we should reset our max and min stats. */ void stl_read(stl_file *stl, int first_facet, bool first) { stl_facet facet; - int i; if (stl->error) return; @@ -268,7 +269,7 @@ void stl_read(stl_file *stl, int first_facet, bool first) { } char normal_buf[3][32]; - for(i = first_facet; i < stl->stats.number_of_facets; i++) { + for(uint32_t i = first_facet; i < stl->stats.number_of_facets; i++) { if(stl->stats.type == binary) /* Read a single facet from a binary .STL file */ { @@ -366,17 +367,19 @@ void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first) } } -void -stl_close(stl_file *stl) { - if (stl->error) return; +void stl_close(stl_file *stl) +{ + assert(stl->fp == nullptr); + assert(stl->heads == nullptr); + assert(stl->tail == nullptr); - if(stl->neighbors_start != NULL) - free(stl->neighbors_start); - if(stl->facet_start != NULL) - free(stl->facet_start); - if(stl->v_indices != NULL) - free(stl->v_indices); - if(stl->v_shared != NULL) - free(stl->v_shared); + if (stl->facet_start != NULL) + free(stl->facet_start); + if (stl->neighbors_start != NULL) + free(stl->neighbors_start); + if (stl->v_indices != NULL) + free(stl->v_indices); + if (stl->v_shared != NULL) + free(stl->v_shared); + memset(stl, 0, sizeof(stl_file)); } - diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 5bd4ea2b69..ce93d95fab 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -189,7 +189,6 @@ target_link_libraries(libslic3r clipper nowide ${EXPAT_LIBRARIES} - ${GLEW_LIBRARIES} glu-libtess polypartition poly2tri diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index e634dd1383..6b16855e8e 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -593,6 +593,8 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs) this->origin_translation = rhs.origin_translation; m_bounding_box = rhs.m_bounding_box; m_bounding_box_valid = rhs.m_bounding_box_valid; + m_raw_bounding_box = rhs.m_raw_bounding_box; + m_raw_bounding_box_valid = rhs.m_raw_bounding_box_valid; m_raw_mesh_bounding_box = rhs.m_raw_mesh_bounding_box; m_raw_mesh_bounding_box_valid = rhs.m_raw_mesh_bounding_box_valid; @@ -627,6 +629,8 @@ ModelObject& ModelObject::assign_copy(ModelObject &&rhs) this->origin_translation = std::move(rhs.origin_translation); m_bounding_box = std::move(rhs.m_bounding_box); m_bounding_box_valid = std::move(rhs.m_bounding_box_valid); + m_raw_bounding_box = rhs.m_raw_bounding_box; + m_raw_bounding_box_valid = rhs.m_raw_bounding_box_valid; m_raw_mesh_bounding_box = rhs.m_raw_mesh_bounding_box; m_raw_mesh_bounding_box_valid = rhs.m_raw_mesh_bounding_box_valid; @@ -859,7 +863,7 @@ TriangleMesh ModelObject::full_raw_mesh() const return mesh; } -BoundingBoxf3 ModelObject::raw_mesh_bounding_box() const +const BoundingBoxf3& ModelObject::raw_mesh_bounding_box() const { if (! m_raw_mesh_bounding_box_valid) { m_raw_mesh_bounding_box_valid = true; @@ -880,33 +884,36 @@ BoundingBoxf3 ModelObject::full_raw_mesh_bounding_box() const } // A transformed snug bounding box around the non-modifier object volumes, without the translation applied. -// This bounding box is only used for the actual slicing. -BoundingBoxf3 ModelObject::raw_bounding_box() const +// This bounding box is only used for the actual slicing and for layer editing UI to calculate the layers. +const BoundingBoxf3& ModelObject::raw_bounding_box() const { - BoundingBoxf3 bb; -#if ENABLE_GENERIC_SUBPARTS_PLACEMENT - if (this->instances.empty()) - throw std::invalid_argument("Can't call raw_bounding_box() with no instances"); + if (! m_raw_bounding_box_valid) { + m_raw_bounding_box_valid = true; + m_raw_bounding_box.reset(); + #if ENABLE_GENERIC_SUBPARTS_PLACEMENT + if (this->instances.empty()) + throw std::invalid_argument("Can't call raw_bounding_box() with no instances"); - const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true); -#endif // ENABLE_GENERIC_SUBPARTS_PLACEMENT - for (const ModelVolume *v : this->volumes) - if (v->is_model_part()) { -#if !ENABLE_GENERIC_SUBPARTS_PLACEMENT - if (this->instances.empty()) - throw std::invalid_argument("Can't call raw_bounding_box() with no instances"); -#endif // !ENABLE_GENERIC_SUBPARTS_PLACEMENT + const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true); + #endif // ENABLE_GENERIC_SUBPARTS_PLACEMENT + for (const ModelVolume *v : this->volumes) + if (v->is_model_part()) { + #if !ENABLE_GENERIC_SUBPARTS_PLACEMENT + if (this->instances.empty()) + throw std::invalid_argument("Can't call raw_bounding_box() with no instances"); + #endif // !ENABLE_GENERIC_SUBPARTS_PLACEMENT - TriangleMesh vol_mesh(v->mesh); -#if ENABLE_GENERIC_SUBPARTS_PLACEMENT - vol_mesh.transform(inst_matrix * v->get_matrix()); - bb.merge(vol_mesh.bounding_box()); -#else - vol_mesh.transform(v->get_matrix()); - bb.merge(this->instances.front()->transform_mesh_bounding_box(vol_mesh, true)); -#endif // ENABLE_GENERIC_SUBPARTS_PLACEMENT - } - return bb; + #if ENABLE_GENERIC_SUBPARTS_PLACEMENT + m_raw_bounding_box.merge(v->mesh.transformed_bounding_box(inst_matrix * v->get_matrix())); + #else + // unmaintaned + assert(false); + // vol_mesh.transform(v->get_matrix()); + // m_raw_bounding_box_valid.merge(this->instances.front()->transform_mesh_bounding_box(vol_mesh, true)); + #endif // ENABLE_GENERIC_SUBPARTS_PLACEMENT + } + } + return m_raw_bounding_box; } // This returns an accurate snug bounding box of the transformed object instance, without the translation applied. @@ -920,13 +927,13 @@ BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_ { if (v->is_model_part()) { - TriangleMesh mesh(v->mesh); #if ENABLE_GENERIC_SUBPARTS_PLACEMENT - mesh.transform(inst_matrix * v->get_matrix()); - bb.merge(mesh.bounding_box()); + bb.merge(v->mesh.transformed_bounding_box(inst_matrix * v->get_matrix())); #else - mesh.transform(v->get_matrix()); - bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(mesh, dont_translate)); + // not maintained + assert(false); + //mesh.transform(v->get_matrix()); + //bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(mesh, dont_translate)); #endif // ENABLE_GENERIC_SUBPARTS_PLACEMENT } } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 9514012434..1234102e0f 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -212,7 +212,7 @@ public: // This bounding box is approximate and not snug. // This bounding box is being cached. const BoundingBoxf3& bounding_box() const; - void invalidate_bounding_box() { m_bounding_box_valid = false; m_raw_mesh_bounding_box_valid = false; } + void invalidate_bounding_box() { m_bounding_box_valid = false; m_raw_bounding_box_valid = false; m_raw_mesh_bounding_box_valid = false; } // A mesh containing all transformed instances of this object. TriangleMesh mesh() const; @@ -223,11 +223,11 @@ public: TriangleMesh full_raw_mesh() const; // A transformed snug bounding box around the non-modifier object volumes, without the translation applied. // This bounding box is only used for the actual slicing. - BoundingBoxf3 raw_bounding_box() const; + const BoundingBoxf3& raw_bounding_box() const; // A snug bounding box around the transformed non-modifier object volumes. BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const; // A snug bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. - BoundingBoxf3 raw_mesh_bounding_box() const; + const BoundingBoxf3& raw_mesh_bounding_box() const; // A snug bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes. BoundingBoxf3 full_raw_mesh_bounding_box() const; @@ -285,7 +285,7 @@ protected: private: ModelObject(Model *model) : m_model(model), origin_translation(Vec3d::Zero()), - m_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {} + m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {} ~ModelObject(); /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */ @@ -304,6 +304,8 @@ private: // Bounding box, cached. mutable BoundingBoxf3 m_bounding_box; mutable bool m_bounding_box_valid; + mutable BoundingBoxf3 m_raw_bounding_box; + mutable bool m_raw_bounding_box_valid; mutable BoundingBoxf3 m_raw_mesh_bounding_box; mutable bool m_raw_mesh_bounding_box_valid; }; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 9e97ab20cb..b566eadeda 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -132,7 +132,7 @@ public: // The slicing parameters are dependent on various configuration values // (layer height, first layer height, raft settings, print nozzle diameter etc). const SlicingParameters& slicing_parameters() const { return m_slicing_params; } - static SlicingParameters slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object); + static SlicingParameters slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z); // returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions) std::vector object_extruders() const; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 46efceb9a8..9fd7787b1b 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2528,14 +2528,17 @@ void PrintConfigDef::init_sla_params() def = this->add("pad_wall_height", coFloat); def->label = L("Pad wall height"); - def->tooltip = L("Defines the cavity depth. Set to zero to disable the cavity."); + def->tooltip = L("Defines the pad cavity depth. Set to zero to disable the cavity. " + "Be careful when enabling this feature, as some resins may " + "produce an extreme suction effect inside the cavity, " + "which makes pealing the print off the vat foil difficult."); def->category = L("Pad"); // def->tooltip = L(""); def->sidetext = L("mm"); def->min = 0; def->max = 30; - def->mode = comSimple; - def->default_value = new ConfigOptionFloat(5.0); + def->mode = comExpert; + def->default_value = new ConfigOptionFloat(0.); def = this->add("pad_max_merge_distance", coFloat); def->label = L("Max merge distance"); @@ -3114,6 +3117,13 @@ CLIMiscConfigDef::CLIMiscConfigDef() def->label = L("Logging level"); def->tooltip = L("Messages with severity lower or eqal to the loglevel will be printed out. 0:trace, 1:debug, 2:info, 3:warning, 4:error, 5:fatal"); def->min = 0; + +#if defined(_MSC_VER) && defined(SLIC3R_GUI) + def = this->add("sw_renderer", coBool); + def->label = L("Render with a software renderer"); + def->tooltip = L("Render with a software renderer. The bundled MESA software renderer is loaded instead of the default OpenGL driver."); + def->min = 0; +#endif /* _MSC_VER */ } const CLIActionsConfigDef cli_actions_config_def; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 954e583f7c..0b51f36ec0 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1370,7 +1370,7 @@ void PrintObject::update_slicing_parameters() this->print()->config(), m_config, unscale(this->size(2)), this->object_extruders()); } -SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object) +SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z) { PrintConfig print_config; PrintObjectConfig object_config; @@ -1390,7 +1390,9 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full object_extruders); sort_remove_duplicates(object_extruders); - return SlicingParameters::create_from_config(print_config, object_config, model_object.bounding_box().max.z(), object_extruders); + if (object_max_z <= 0.f) + object_max_z = model_object.raw_bounding_box().size().z(); + return SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders); } // returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 34dd80cee0..bb0e5e0071 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -1572,10 +1572,8 @@ public: auto hit = bridge_mesh_intersect(headjp, n, r); if(std::isinf(hit.distance())) ground_head_indices.emplace_back(i); - else { - if(m_cfg.ground_facing_only) head.invalidate(); - m_iheads_onmodel.emplace_back(std::make_pair(i, hit)); - } + else if(m_cfg.ground_facing_only) head.invalidate(); + else m_iheads_onmodel.emplace_back(std::make_pair(i, hit)); } // We want to search for clusters of points that are far enough @@ -1872,7 +1870,7 @@ public: } } - void cascade_pillars() { + void interconnect_pillars() { // Now comes the algorithm that connects pillars with each other. // Ideally every pillar should be connected with at least one of its // neighbors if that neighbor is within max_pillar_link_distance @@ -2121,7 +2119,7 @@ bool SLASupportTree::generate(const std::vector &support_points, std::bind(&Algorithm::routing_to_model, &alg), - std::bind(&Algorithm::cascade_pillars, &alg), + std::bind(&Algorithm::interconnect_pillars, &alg), std::bind(&Algorithm::routing_headless, &alg), @@ -2150,16 +2148,16 @@ bool SLASupportTree::generate(const std::vector &support_points, // Let's define a simple automaton that will run our program. auto progress = [&ctl, &pc] () { static const std::array stepstr { - L("Starting"), - L("Filtering"), - L("Generate pinheads"), - L("Classification"), - L("Routing to ground"), - L("Routing supports to model surface"), - L("Cascading pillars"), - L("Processing small holes"), - L("Done"), - L("Abort") + "Starting", + "Filtering", + "Generate pinheads", + "Classification", + "Routing to ground", + "Routing supports to model surface", + "Interconnecting pillars", + "Processing small holes", + "Done", + "Abort" }; static const std::array stepstate { diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index bfba364af6..2d603661dc 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -693,6 +693,16 @@ void TriangleMeshSlicer::init(TriangleMesh *_mesh, throw_on_cancel_callback_type } } + + +void TriangleMeshSlicer::set_up_direction(const Vec3f& up) +{ + m_quaternion.setFromTwoVectors(up, Vec3f::UnitZ()); + m_use_quaternion = true; +} + + + void TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers, throw_on_cancel_callback_type throw_on_cancel) const { BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice"; @@ -795,7 +805,7 @@ void TriangleMeshSlicer::slice(const std::vector &z, std::vector* lines, boost::mutex* lines_mutex, const std::vector &z) const { - const stl_facet &facet = this->mesh->stl.facet_start[facet_idx]; + const stl_facet &facet = m_use_quaternion ? this->mesh->stl.facet_start[facet_idx].rotated(m_quaternion) : this->mesh->stl.facet_start[facet_idx]; // find facet extents const float min_z = fminf(facet.vertex[0](2), fminf(facet.vertex[1](2), facet.vertex[2](2))); @@ -860,26 +870,43 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( IntersectionPoint points[3]; size_t num_points = 0; size_t point_on_layer = size_t(-1); - + // Reorder vertices so that the first one is the one with lowest Z. // This is needed to get all intersection lines in a consistent order // (external on the right of the line) const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; int i = (facet.vertex[1].z() == min_z) ? 1 : ((facet.vertex[2].z() == min_z) ? 2 : 0); + + // These are used only if the cut plane is tilted: + stl_vertex rotated_a; + stl_vertex rotated_b; + for (int j = i; j - i < 3; ++j) { // loop through facet edges int edge_id = this->facets_edges[facet_idx * 3 + (j % 3)]; int a_id = vertices[j % 3]; int b_id = vertices[(j+1) % 3]; - const stl_vertex *a = &this->v_scaled_shared[a_id]; - const stl_vertex *b = &this->v_scaled_shared[b_id]; + + const stl_vertex *a; + const stl_vertex *b; + if (m_use_quaternion) { + rotated_a = m_quaternion * this->v_scaled_shared[a_id]; + rotated_b = m_quaternion * this->v_scaled_shared[b_id]; + a = &rotated_a; + b = &rotated_b; + } + else { + a = &this->v_scaled_shared[a_id]; + b = &this->v_scaled_shared[b_id]; + } // Is edge or face aligned with the cutting plane? if (a->z() == slice_z && b->z() == slice_z) { // Edge is horizontal and belongs to the current layer. - const stl_vertex &v0 = this->v_scaled_shared[vertices[0]]; - const stl_vertex &v1 = this->v_scaled_shared[vertices[1]]; - const stl_vertex &v2 = this->v_scaled_shared[vertices[2]]; - const stl_normal &normal = this->mesh->stl.facet_start[facet_idx].normal; + // The following rotation of the three vertices may not be efficient, but this branch happens rarely. + const stl_vertex &v0 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[0]]) : this->v_scaled_shared[vertices[0]]; + const stl_vertex &v1 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[1]]) : this->v_scaled_shared[vertices[1]]; + const stl_vertex &v2 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[2]]) : this->v_scaled_shared[vertices[2]]; + const stl_normal &normal = facet.normal; // We may ignore this edge for slicing purposes, but we may still use it for object cutting. FacetSliceType result = Slicing; if (min_z == max_z) { @@ -995,7 +1022,9 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( if (i == line_out->a_id || i == line_out->b_id) i = vertices[2]; assert(i != line_out->a_id && i != line_out->b_id); - line_out->edge_type = (this->v_scaled_shared[i].z() < slice_z) ? feTop : feBottom; + line_out->edge_type = ((m_use_quaternion ? + (m_quaternion * this->v_scaled_shared[i]).z() + : this->v_scaled_shared[i].z()) < slice_z) ? feTop : feBottom; } #endif return Slicing; diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index d389500c69..4bf5ce039f 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -25,9 +25,10 @@ public: TriangleMesh(const Pointf3s &points, const std::vector &facets); TriangleMesh(const TriangleMesh &other) : repaired(false) { stl_initialize(&this->stl); *this = other; } TriangleMesh(TriangleMesh &&other) : repaired(false) { stl_initialize(&this->stl); this->swap(other); } - ~TriangleMesh() { stl_close(&this->stl); } + ~TriangleMesh() { clear(); } TriangleMesh& operator=(const TriangleMesh &other); TriangleMesh& operator=(TriangleMesh &&other) { this->swap(other); return *this; } + void clear() { stl_close(&this->stl); this->repaired = false; } void swap(TriangleMesh &other) { std::swap(this->stl, other.stl); std::swap(this->repaired, other.repaired); } void ReadSTLFile(const char* input_file) { stl_open(&stl, input_file); } void write_ascii(const char* output_file) { stl_write_ascii(&this->stl, output_file, ""); } @@ -171,6 +172,7 @@ public: FacetSliceType slice_facet(float slice_z, const stl_facet &facet, const int facet_idx, const float min_z, const float max_z, IntersectionLine *line_out) const; void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const; + void set_up_direction(const Vec3f& up); private: const TriangleMesh *mesh; @@ -178,6 +180,10 @@ private: std::vector facets_edges; // Scaled copy of this->mesh->stl.v_shared std::vector v_scaled_shared; + // Quaternion that will be used to rotate every facet before the slicing + Eigen::Quaternion m_quaternion; + // Whether or not the above quaterion should be used + bool m_use_quaternion = false; void _slice_do(size_t facet_idx, std::vector* lines, boost::mutex* lines_mutex, const std::vector &z) const; void make_loops(std::vector &lines, Polygons* loops) const; diff --git a/src/slic3r.cpp b/src/slic3r.cpp index 780efea7b3..ff87b3f6d9 100644 --- a/src/slic3r.cpp +++ b/src/slic3r.cpp @@ -6,10 +6,12 @@ #define NOMINMAX #include #include - // Let the NVIDIA and AMD know we want to use their graphics card - // on a dual graphics card system. - __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; - __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; + #ifdef SLIC3R_GUI + // Let the NVIDIA and AMD know we want to use their graphics card + // on a dual graphics card system. + __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; + __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; + #endif /* SLIC3R_GUI */ #endif /* WIN32 */ #include @@ -38,8 +40,11 @@ #include "libslic3r/Utils.hpp" #include "slic3r.hpp" -#include "slic3r/GUI/GUI.hpp" -#include "slic3r/GUI/GUI_App.hpp" + +#ifdef SLIC3R_GUI + #include "slic3r/GUI/GUI.hpp" + #include "slic3r/GUI/GUI_App.hpp" +#endif /* SLIC3R_GUI */ using namespace Slic3r; @@ -448,7 +453,7 @@ int CLI::run(int argc, char **argv) } if (start_gui) { -#if 1 +#ifdef SLIC3R_GUI // #ifdef USE_WX GUI::GUI_App *gui = new GUI::GUI_App(); // gui->autosave = m_config.opt_string("autosave"); @@ -477,12 +482,12 @@ int CLI::run(int argc, char **argv) gui->mainframe->load_config(m_extra_config); }); return wxEntry(argc, argv); -#else +#else /* SLIC3R_GUI */ // No GUI support. Just print out a help. this->print_help(false); // If started without a parameter, consider it to be OK, otherwise report an error code (no action etc). return (argc == 0) ? 0 : 1; -#endif +#endif /* SLIC3R_GUI */ } return 0; @@ -563,7 +568,13 @@ bool CLI::setup(int argc, char **argv) void CLI::print_help(bool include_print_options, PrinterTechnology printer_technology) const { boost::nowide::cout - << "Slic3r Prusa Edition " << SLIC3R_BUILD << std::endl + << "Slic3r Prusa Edition " << SLIC3R_BUILD +#ifdef SLIC3R_GUI + << " (with GUI support)" +#else /* SLIC3R_GUI */ + << " (without GUI support)" +#endif /* SLIC3R_GUI */ + << std::endl << "https://github.com/prusa3d/Slic3r" << std::endl << std::endl << "Usage: slic3r [ ACTIONS ] [ TRANSFORM ] [ OPTIONS ] [ file.stl ... ]" << std::endl << std::endl diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 9d25318136..1d8a1e26e3 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -154,7 +154,7 @@ endif () add_library(libslic3r_gui STATIC ${SLIC3R_GUI_SOURCES}) -target_link_libraries(libslic3r_gui libslic3r avrdude imgui) +target_link_libraries(libslic3r_gui libslic3r avrdude imgui ${GLEW_LIBRARIES}) if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE) endif () diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 3dd680820c..9038e388c8 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -721,32 +721,37 @@ int GLVolumeCollection::load_wipe_tower_preview( return int(this->volumes.size() - 1); } -typedef std::pair GLVolumeWithZ; -typedef std::vector GLVolumesWithZList; -static GLVolumesWithZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function filter_func) +GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function filter_func) { - GLVolumesWithZList list; + GLVolumeWithIdAndZList list; list.reserve(volumes.size()); - for (GLVolume* volume : volumes) + for (unsigned int i = 0; i < (unsigned int)volumes.size(); ++i) { + GLVolume* volume = volumes[i]; bool is_transparent = (volume->render_color[3] < 1.0f); if ((((type == GLVolumeCollection::Opaque) && !is_transparent) || ((type == GLVolumeCollection::Transparent) && is_transparent) || (type == GLVolumeCollection::All)) && (! filter_func || filter_func(*volume))) - list.emplace_back(std::make_pair(volume, 0.0)); + list.emplace_back(std::make_pair(volume, std::make_pair(i, 0.0))); } if ((type == GLVolumeCollection::Transparent) && (list.size() > 1)) { - for (GLVolumeWithZ& volume : list) + for (GLVolumeWithIdAndZ& volume : list) { - volume.second = volume.first->bounding_box.transformed(view_matrix * volume.first->world_matrix()).max(2); + volume.second.second = volume.first->bounding_box.transformed(view_matrix * volume.first->world_matrix()).max(2); } std::sort(list.begin(), list.end(), - [](const GLVolumeWithZ& v1, const GLVolumeWithZ& v2) -> bool { return v1.second < v2.second; } + [](const GLVolumeWithIdAndZ& v1, const GLVolumeWithIdAndZ& v2) -> bool { return v1.second.second < v2.second.second; } + ); + } + else if ((type == GLVolumeCollection::Opaque) && (list.size() > 1)) + { + std::sort(list.begin(), list.end(), + [](const GLVolumeWithIdAndZ& v1, const GLVolumeWithIdAndZ& v2) -> bool { return v1.first->selected && !v2.first->selected; } ); } @@ -769,6 +774,7 @@ void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id)); GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1; GLint z_range_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "z_range") : -1; + GLint clipping_plane_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "clipping_plane") : -1; GLint print_box_min_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.min") : -1; GLint print_box_max_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.max") : -1; GLint print_box_detection_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.volume_detection") : -1; @@ -784,8 +790,11 @@ void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool if (z_range_id != -1) glsafe(::glUniform2fv(z_range_id, 1, (const GLfloat*)z_range)); - GLVolumesWithZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); - for (GLVolumeWithZ& volume : to_render) { + if (clipping_plane_id != -1) + glsafe(::glUniform4fv(clipping_plane_id, 1, (const GLfloat*)clipping_plane)); + + GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); + for (GLVolumeWithIdAndZ& volume : to_render) { volume.first->set_render_color(); volume.first->render_VBOs(color_id, print_box_detection_id, print_box_worldmatrix_id); } @@ -814,8 +823,8 @@ void GLVolumeCollection::render_legacy(ERenderType type, bool disable_cullface, glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - GLVolumesWithZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); - for (GLVolumeWithZ& volume : to_render) + GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); + for (GLVolumeWithIdAndZ& volume : to_render) { volume.first->set_render_color(); volume.first->render_legacy(); diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index ce7bf8e975..88547359ea 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -412,6 +412,8 @@ public: }; typedef std::vector GLVolumePtrs; +typedef std::pair> GLVolumeWithIdAndZ; +typedef std::vector GLVolumeWithIdAndZList; class GLVolumeCollection { @@ -431,6 +433,9 @@ private: // z range for clipping in shaders float z_range[2]; + // plane coeffs for clipping in shaders + float clipping_plane[4]; + public: GLVolumePtrs volumes; @@ -489,6 +494,7 @@ public: } void set_z_range(float min_z, float max_z) { z_range[0] = min_z; z_range[1] = max_z; } + void set_clipping_plane(const double* coeffs) { clipping_plane[0] = coeffs[0]; clipping_plane[1] = coeffs[1]; clipping_plane[2] = coeffs[2]; clipping_plane[3] = coeffs[3]; } // returns true if all the volumes are completely contained in the print volume // returns the containment state in the given out_state, if non-null @@ -505,6 +511,8 @@ private: GLVolumeCollection& operator=(const GLVolumeCollection &); }; +GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function filter_func = nullptr); + class GLModel { protected: diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 29c64b9a5c..0dc3ec83a8 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -255,16 +255,21 @@ void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config) void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id) { const ModelObject *model_object_new = (object_id >= 0) ? model.objects[object_id] : nullptr; - if (model_object_new == nullptr || this->last_object_id != object_id || m_model_object != model_object_new || m_model_object->id() != model_object_new->id()) { + // Maximum height of an object changes when the object gets rotated or scaled. + // Changing maximum height of an object will invalidate the layer heigth editing profile. + // m_model_object->raw_bounding_box() is cached, therefore it is cheap even if this method is called frequently. + float new_max_z = (m_model_object == nullptr) ? 0.f : m_model_object->raw_bounding_box().size().z(); + if (m_model_object != model_object_new || this->last_object_id != object_id || m_object_max_z != new_max_z || + (model_object_new != nullptr && m_model_object->id() != model_object_new->id())) { m_layer_height_profile.clear(); m_layer_height_profile_modified = false; delete m_slicing_parameters; - m_slicing_parameters = nullptr; + m_slicing_parameters = nullptr; m_layers_texture.valid = false; + this->last_object_id = object_id; + m_model_object = model_object_new; + m_object_max_z = new_max_z; } - this->last_object_id = object_id; - m_model_object = model_object_new; - m_object_max_z = (m_model_object == nullptr) ? 0.f : m_model_object->bounding_box().max.z(); } bool GLCanvas3D::LayersEditing::is_allowed() const @@ -623,7 +628,7 @@ void GLCanvas3D::LayersEditing::update_slicing_parameters() { if (m_slicing_parameters == nullptr) { m_slicing_parameters = new SlicingParameters(); - *m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object); + *m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z); } } @@ -2204,6 +2209,9 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) void GLCanvas3D::on_char(wxKeyEvent& evt) { + if (!m_initialized) + return; + // see include/wx/defs.h enum wxKeyCode int keyCode = evt.GetKeyCode(); int ctrlMask = wxMOD_CONTROL; @@ -2222,9 +2230,12 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) //#endif /* __APPLE__ */ if ((evt.GetModifiers() & ctrlMask) != 0) { switch (keyCode) { +#ifdef __APPLE__ case 'a': case 'A': +#else /* __APPLE__ */ case WXK_CONTROL_A: +#endif /* __APPLE__ */ post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL)); break; #ifdef __APPLE__ @@ -3134,6 +3145,11 @@ Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos) return Linef3(_mouse_to_3d(mouse_pos, &z0), _mouse_to_3d(mouse_pos, &z1)); } +double GLCanvas3D::get_size_proportional_to_max_bed_size(double factor) const +{ + return factor * m_bed.get_bounding_box().max_size(); +} + bool GLCanvas3D::_is_shown_on_screen() const { return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false; @@ -3226,12 +3242,37 @@ bool GLCanvas3D::_init_toolbar() if (!m_toolbar.add_separator()) return false; + item.name = "copy"; +#if ENABLE_SVG_ICONS + item.icon_filename = "copy.svg"; +#endif // ENABLE_SVG_ICONS + item.tooltip = GUI::L_str("Copy") + " [" + GUI::shortkey_ctrl_prefix() + "C]"; + item.sprite_id = 4; + item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); }; + item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_copy(); }; + if (!m_toolbar.add_item(item)) + return false; + + item.name = "paste"; +#if ENABLE_SVG_ICONS + item.icon_filename = "paste.svg"; +#endif // ENABLE_SVG_ICONS + item.tooltip = GUI::L_str("Paste") + " [" + GUI::shortkey_ctrl_prefix() + "V]"; + item.sprite_id = 5; + item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); }; + item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_paste(); }; + if (!m_toolbar.add_item(item)) + return false; + + if (!m_toolbar.add_separator()) + return false; + item.name = "more"; #if ENABLE_SVG_ICONS item.icon_filename = "instance_add.svg"; #endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Add instance [+]"); - item.sprite_id = 4; + item.sprite_id = 6; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); }; @@ -3243,7 +3284,7 @@ bool GLCanvas3D::_init_toolbar() item.icon_filename = "instance_remove.svg"; #endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Remove instance [-]"); - item.sprite_id = 5; + item.sprite_id = 7; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); }; @@ -3258,7 +3299,7 @@ bool GLCanvas3D::_init_toolbar() item.icon_filename = "split_objects.svg"; #endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Split to objects"); - item.sprite_id = 6; + item.sprite_id = 8; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); }; item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); }; @@ -3270,7 +3311,7 @@ bool GLCanvas3D::_init_toolbar() item.icon_filename = "split_parts.svg"; #endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Split to parts"); - item.sprite_id = 7; + item.sprite_id = 9; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); }; @@ -3285,7 +3326,7 @@ bool GLCanvas3D::_init_toolbar() item.icon_filename = "layers.svg"; #endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Layers editing"); - item.sprite_id = 8; + item.sprite_id = 10; item.is_toggable = true; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; item.visibility_callback = [this]()->bool { return m_process->current_printer_technology() == ptFFF; }; @@ -3490,7 +3531,15 @@ void GLCanvas3D::_picking_pass() const glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); - _render_volumes(true); + m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane(); + if (m_camera_clipping_plane.is_active()) { + ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data()); + ::glEnable(GL_CLIP_PLANE0); + } + _render_volumes_for_picking(); + if (m_camera_clipping_plane.is_active()) + ::glDisable(GL_CLIP_PLANE0); + m_gizmos.render_current_gizmo_for_picking_pass(m_selection); if (m_multisample_allowed) @@ -3571,6 +3620,8 @@ void GLCanvas3D::_render_axes() const m_bed.render_axes(); } + + void GLCanvas3D::_render_objects() const { if (m_volumes.empty()) @@ -3579,6 +3630,8 @@ void GLCanvas3D::_render_objects() const glsafe(::glEnable(GL_LIGHTING)); glsafe(::glEnable(GL_DEPTH_TEST)); + m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane(); + if (m_use_VBOs) { if (m_picking_enabled) @@ -3599,6 +3652,8 @@ void GLCanvas3D::_render_objects() const else m_volumes.set_z_range(-FLT_MAX, FLT_MAX); + m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); + m_shader.start_using(); if (m_picking_enabled && m_layers_editing.is_enabled() && m_layers_editing.last_object_id != -1) { int object_id = m_layers_editing.last_object_id; @@ -3619,13 +3674,17 @@ void GLCanvas3D::_render_objects() const } else { + ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data()); + ::glEnable(GL_CLIP_PLANE0); + if (m_use_clipping_planes) { - glsafe(::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_clipping_planes[0].get_data())); - glsafe(::glEnable(GL_CLIP_PLANE0)); - glsafe(::glClipPlane(GL_CLIP_PLANE1, (GLdouble*)m_clipping_planes[1].get_data())); + glsafe(::glClipPlane(GL_CLIP_PLANE1, (GLdouble*)m_clipping_planes[0].get_data())); glsafe(::glEnable(GL_CLIP_PLANE1)); + glsafe(::glClipPlane(GL_CLIP_PLANE2, (GLdouble*)m_clipping_planes[1].get_data())); + glsafe(::glEnable(GL_CLIP_PLANE2)); } + // do not cull backfaces to show broken geometry, if any m_volumes.render_legacy(GLVolumeCollection::Opaque, m_picking_enabled, m_camera.get_view_matrix(), [this](const GLVolume& volume) { @@ -3633,13 +3692,16 @@ void GLCanvas3D::_render_objects() const }); m_volumes.render_legacy(GLVolumeCollection::Transparent, false, m_camera.get_view_matrix()); + ::glDisable(GL_CLIP_PLANE0); + if (m_use_clipping_planes) { - glsafe(::glDisable(GL_CLIP_PLANE0)); glsafe(::glDisable(GL_CLIP_PLANE1)); + glsafe(::glDisable(GL_CLIP_PLANE2)); } } - + + m_camera_clipping_plane = ClippingPlane::ClipsNothing(); glsafe(::glDisable(GL_LIGHTING)); } @@ -3675,13 +3737,10 @@ void GLCanvas3D::_render_legend_texture() const m_legend_texture.render(*this); } -void GLCanvas3D::_render_volumes(bool fake_colors) const +void GLCanvas3D::_render_volumes_for_picking() const { static const GLfloat INV_255 = 1.0f / 255.0f; - if (!fake_colors) - glsafe(::glEnable(GL_LIGHTING)); - // do not cull backfaces to show broken geometry, if any glsafe(::glDisable(GL_CULL_FACE)); @@ -3691,27 +3750,31 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - unsigned int volume_id = 0; - for (GLVolume* vol : m_volumes.volumes) + const Transform3d& view_matrix = m_camera.get_view_matrix(); + GLVolumeWithIdAndZList to_render = volumes_to_render(m_volumes.volumes, GLVolumeCollection::Opaque, view_matrix); + for (const GLVolumeWithIdAndZ& volume : to_render) { - if (fake_colors) - { - // Object picking mode. Render the object with a color encoding the object index. - unsigned int r = (volume_id & 0x000000FF) >> 0; - unsigned int g = (volume_id & 0x0000FF00) >> 8; - unsigned int b = (volume_id & 0x00FF0000) >> 16; - glsafe(::glColor3f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255)); - } - else - { - vol->set_render_color(); - glsafe(::glColor4fv(vol->render_color)); - } + // Object picking mode. Render the object with a color encoding the object index. + unsigned int r = (volume.second.first & 0x000000FF) >> 0; + unsigned int g = (volume.second.first & 0x0000FF00) >> 8; + unsigned int b = (volume.second.first & 0x00FF0000) >> 16; + glsafe(::glColor3f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255)); - if ((!fake_colors || !vol->disabled) && (vol->composite_id.volume_id >= 0 || m_render_sla_auxiliaries)) - vol->render(); + if (!volume.first->disabled && ((volume.first->composite_id.volume_id >= 0) || m_render_sla_auxiliaries)) + volume.first->render(); + } - ++volume_id; + to_render = volumes_to_render(m_volumes.volumes, GLVolumeCollection::Transparent, view_matrix); + for (const GLVolumeWithIdAndZ& volume : to_render) + { + // Object picking mode. Render the object with a color encoding the object index. + unsigned int r = (volume.second.first & 0x000000FF) >> 0; + unsigned int g = (volume.second.first & 0x0000FF00) >> 8; + unsigned int b = (volume.second.first & 0x00FF0000) >> 16; + glsafe(::glColor3f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255)); + + if (!volume.first->disabled && ((volume.first->composite_id.volume_id >= 0) || m_render_sla_auxiliaries)) + volume.first->render(); } glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); @@ -3719,9 +3782,6 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const glsafe(::glDisable(GL_BLEND)); glsafe(::glEnable(GL_CULL_FACE)); - - if (!fake_colors) - glsafe(::glDisable(GL_LIGHTING)); } void GLCanvas3D::_render_current_gizmo() const @@ -3999,15 +4059,9 @@ void GLCanvas3D::_update_volumes_hover_state() const return; GLVolume* volume = m_volumes.volumes[m_hover_volume_id]; - - switch (m_selection.get_mode()) - { - case Selection::Volume: - { + if (volume->is_modifier) volume->hover = true; - break; - } - case Selection::Instance: + else { int object_idx = volume->object_idx(); int instance_idx = volume->instance_idx(); @@ -4017,9 +4071,6 @@ void GLCanvas3D::_update_volumes_hover_state() const if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) v->hover = true; } - - break; - } } } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 53551a4728..e81d46f117 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -65,6 +65,37 @@ public: void set_scale_factor(int height); }; + +class ClippingPlane +{ + double m_data[4]; + +public: + ClippingPlane() + { + m_data[0] = 0.0; + m_data[1] = 0.0; + m_data[2] = 1.0; + m_data[3] = 0.0; + } + + ClippingPlane(const Vec3d& direction, double offset) + { + Vec3d norm_dir = direction.normalized(); + m_data[0] = norm_dir(0); + m_data[1] = norm_dir(1); + m_data[2] = norm_dir(2); + m_data[3] = offset; + } + + bool is_active() const { return m_data[3] != DBL_MAX; } + + static ClippingPlane ClipsNothing() { return ClippingPlane(Vec3d(0., 0., 1.), DBL_MAX); } + + const double* get_data() const { return m_data; } +}; + + wxDECLARE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); using Vec2dEvent = Event; @@ -288,32 +319,6 @@ class GLCanvas3D } }; -public: - class ClippingPlane - { - double m_data[4]; - - public: - ClippingPlane() - { - m_data[0] = 0.0; - m_data[1] = 0.0; - m_data[2] = 1.0; - m_data[3] = 0.0; - } - - ClippingPlane(const Vec3d& direction, double offset) - { - Vec3d norm_dir = direction.normalized(); - m_data[0] = norm_dir(0); - m_data[1] = norm_dir(1); - m_data[2] = norm_dir(2); - m_data[3] = offset; - } - - const double* get_data() const { return m_data; } - }; - private: struct SlaCap { @@ -405,6 +410,7 @@ private: mutable GLGizmosManager m_gizmos; mutable GLToolbar m_toolbar; ClippingPlane m_clipping_planes[2]; + mutable ClippingPlane m_camera_clipping_plane; bool m_use_clipping_planes; mutable SlaCap m_sla_caps[2]; std::string m_sidebar_field; @@ -579,6 +585,8 @@ public: void refresh_camera_scene_box() { m_camera.set_scene_box(scene_bounding_box()); } bool is_mouse_dragging() const { return m_mouse.dragging; } + double get_size_proportional_to_max_bed_size(double factor) const; + private: bool _is_shown_on_screen() const; @@ -605,7 +613,7 @@ private: #endif // ENABLE_RENDER_SELECTION_CENTER void _render_warning_texture() const; void _render_legend_texture() const; - void _render_volumes(bool fake_colors) const; + void _render_volumes_for_picking() const; void _render_current_gizmo() const; void _render_gizmos_overlay() const; void _render_toolbar() const; diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 144d02476e..842700aef8 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -21,6 +21,8 @@ wxDEFINE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLTOOLBAR_COPY, SimpleEvent); +wxDEFINE_EVENT(EVT_GLTOOLBAR_PASTE, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_FEWER, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_SPLIT_OBJECTS, SimpleEvent); diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 0f8b17e04f..24314d60ff 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -20,6 +20,8 @@ wxDECLARE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent); +wxDECLARE_EVENT(EVT_GLTOOLBAR_COPY, SimpleEvent); +wxDECLARE_EVENT(EVT_GLTOOLBAR_PASTE, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_FEWER, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_SPLIT_OBJECTS, SimpleEvent); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index c3bc808f40..b9cd2fc98a 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -61,19 +61,22 @@ ObjectList::ObjectList(wxWindow* parent) : { // Fill CATEGORY_ICON { + // Note: `this` isn't passed to create_scaled_bitmap() here because of bugs in the widget, + // see note in PresetBundle::load_compatible_bitmaps() + // ptFFF - CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap(this, "layers"); - CATEGORY_ICON[L("Infill")] = create_scaled_bitmap(this, "infill"); - CATEGORY_ICON[L("Support material")] = create_scaled_bitmap(this, "support"); - CATEGORY_ICON[L("Speed")] = create_scaled_bitmap(this, "time"); - CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap(this, "funnel"); - CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap(this, "funnel"); -// CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap(this, "skirt+brim"); -// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap(this, "time"); - CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap(this, "wrench"); - // ptSLA - CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(this, "sla_supports"); - CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(this, "brick.png"); + CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap(nullptr, "layers"); + CATEGORY_ICON[L("Infill")] = create_scaled_bitmap(nullptr, "infill"); + CATEGORY_ICON[L("Support material")] = create_scaled_bitmap(nullptr, "support"); + CATEGORY_ICON[L("Speed")] = create_scaled_bitmap(nullptr, "time"); + CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap(nullptr, "funnel"); + CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap(nullptr, "funnel"); +// CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap(nullptr, "skirt+brim"); +// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap(nullptr, "time"); + CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap(nullptr, "wrench"); + // ptSLA + CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(nullptr, "support"/*"sla_supports"*/); + CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(nullptr, "pad"); } // create control @@ -392,10 +395,10 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const void ObjectList::init_icons() { - m_bmp_modifiermesh = create_scaled_bitmap(this, "lambda.png"); - m_bmp_solidmesh = create_scaled_bitmap(this, "object.png"); - m_bmp_support_enforcer = create_scaled_bitmap(this, "support_enforcer_.png"); - m_bmp_support_blocker = create_scaled_bitmap(this, "support_blocker_.png"); + m_bmp_modifiermesh = create_scaled_bitmap(nullptr, "add_modifier"); + m_bmp_solidmesh = create_scaled_bitmap(nullptr, "add_part"); + m_bmp_support_enforcer = create_scaled_bitmap(nullptr, "support_enforcer"); + m_bmp_support_blocker = create_scaled_bitmap(nullptr, "support_blocker"); m_bmp_vector.reserve(4); // bitmaps for different types of parts @@ -406,13 +409,13 @@ void ObjectList::init_icons() m_objects_model->SetVolumeBitmaps(m_bmp_vector); // init icon for manifold warning - m_bmp_manifold_warning = create_scaled_bitmap(this, "exclamation_mark_.png"); + m_bmp_manifold_warning = create_scaled_bitmap(nullptr, "exclamation"); // init bitmap for "Split to sub-objects" context menu - m_bmp_split = create_scaled_bitmap(this, "split_parts"); + m_bmp_split = create_scaled_bitmap(nullptr, "split_parts_SMALL"); // init bitmap for "Add Settings" context menu - m_bmp_cog = create_scaled_bitmap(this, "cog"); + m_bmp_cog = create_scaled_bitmap(nullptr, "cog"); } @@ -436,6 +439,66 @@ void ObjectList::selection_changed() part_selection_changed(); } +void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes) +{ + if ((obj_idx < 0) || ((int)m_objects->size() <= obj_idx)) + return; + + if (volumes.empty()) + return; + + ModelObject& model_object = *(*m_objects)[obj_idx]; + const auto object_item = m_objects_model->GetItemById(obj_idx); + + wxDataViewItemArray items; + + for (const ModelVolume* volume : volumes) + { + auto vol_item = m_objects_model->AddVolumeChild(object_item, volume->name, volume->type(), + volume->config.has("extruder") ? volume->config.option("extruder")->value : 0); + auto opt_keys = volume->config.keys(); + if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder"))) + select_item(m_objects_model->AddSettingsChild(vol_item)); + + items.Add(vol_item); + } + + m_parts_changed = true; + parts_changed(obj_idx); + + if (items.size() > 1) + { + m_selection_mode = smVolume; + m_last_selected_item = wxDataViewItem(0); + } + + select_items(items); +#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME + selection_changed(); +#endif //no __WXOSX__ //__WXMSW__ +} + +void ObjectList::paste_objects_into_list(const std::vector& object_idxs) +{ + if (object_idxs.empty()) + return; + + wxDataViewItemArray items; + for (const size_t object : object_idxs) + { + add_object_to_list(object); + m_parts_changed = true; + parts_changed(object); + + items.Add(m_objects_model->GetItemById(object)); + } + + select_items(items); +#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME + selection_changed(); +#endif //no __WXOSX__ //__WXMSW__ +} + void ObjectList::OnChar(wxKeyEvent& event) { if (event.GetKeyCode() == WXK_BACK){ @@ -1374,9 +1437,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode const wxString name = _(L("Generic")) + "-" + _(type_name); TriangleMesh mesh; - auto& bed_shape = printer_config().option("bed_shape")->values; - const auto& sz = BoundingBoxf(bed_shape).size(); - const auto side = 0.1 * std::max(sz(0), sz(1)); + double side = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.1); if (type_name == "Box") // Sitting on the print bed, left front front corner at (0, 0). @@ -1573,11 +1634,11 @@ void ObjectList::split() for (auto id = 0; id < model_object->volumes.size(); id++) { const auto vol_item = m_objects_model->AddVolumeChild(parent, from_u8(model_object->volumes[id]->name), - model_object->volumes[id]->is_modifier() ? - ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART, - model_object->volumes[id]->config.has("extruder") ? - model_object->volumes[id]->config.option("extruder")->value : 0, - false); + model_object->volumes[id]->is_modifier() ? + ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART, + model_object->volumes[id]->config.has("extruder") ? + model_object->volumes[id]->config.option("extruder")->value : 0, + false); // add settings to the part, if it has those auto opt_keys = model_object->volumes[id]->config.keys(); if ( !(opt_keys.size() == 1 && opt_keys[0] == "extruder") ) { @@ -2132,6 +2193,7 @@ void ObjectList::update_selections_on_canvas() add_to_selection(item, selection, instance_idx, false); wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); + wxGetApp().plater()->canvas3D()->render(); } void ObjectList::select_item(const wxDataViewItem& item) diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index c3d76f656a..d1651748e1 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -31,6 +31,8 @@ typedef std::map> FreqSettingsBundle; // category -> vector ( option ; label ) typedef std::map< std::string, std::vector< std::pair > > settings_menu_hierarchy; +typedef std::vector ModelVolumePtrs; + namespace GUI { wxDECLARE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent); @@ -286,6 +288,9 @@ public: void fix_through_netfabb() const; void update_item_error_icon(const int obj_idx, int vol_idx) const ; + void paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes); + void paste_objects_into_list(const std::vector& object_idxs); + void rescale(); private: diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 438e9d236a..206253451a 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -818,8 +818,8 @@ void Preview::on_sliders_scroll_changed(wxEvent& event) } else if (tech == ptSLA) { - m_canvas->set_clipping_plane(0, GLCanvas3D::ClippingPlane(Vec3d::UnitZ(), -m_slider->GetLowerValueD())); - m_canvas->set_clipping_plane(1, GLCanvas3D::ClippingPlane(-Vec3d::UnitZ(), m_slider->GetHigherValueD())); + m_canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -m_slider->GetLowerValueD())); + m_canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), m_slider->GetHigherValueD())); m_canvas->set_use_clipping_planes(m_slider->GetHigherValue() != 0); m_canvas_widget->Refresh(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 67abfdd35a..aa88f9dd59 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -10,6 +10,7 @@ #include "slic3r/GUI/GUI_ObjectSettings.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/PresetBundle.hpp" +#include "libslic3r/Tesselate.hpp" namespace Slic3r { @@ -47,24 +48,24 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S { if (selection.is_empty()) { m_model_object = nullptr; - m_old_model_object = nullptr; return; } - m_old_model_object = m_model_object; m_model_object = model_object; m_active_instance = selection.get_instance_idx(); if (model_object && selection.is_from_single_instance()) { + // Cache the bb - it's needed for dealing with the clipping plane quite often + // It could be done inside update_mesh but one has to account for scaling of the instance. + //FIXME calling ModelObject::instance_bounding_box() is expensive! + m_active_instance_bb_radius = m_model_object->instance_bounding_box(m_active_instance).radius(); + if (is_mesh_update_necessary()) { update_mesh(); editing_mode_reload_cache(); } - if (m_model_object != m_old_model_object) - m_editing_mode = false; - if (m_editing_mode_cache.empty() && m_model_object->sla_points_status != sla::PointsStatus::UserModified) get_data_from_backend(); @@ -88,12 +89,75 @@ void GLGizmoSlaSupports::on_render(const Selection& selection) const glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_DEPTH_TEST)); - render_points(selection, false); + // we'll recover current look direction from the modelview matrix (in world coords): + Eigen::Matrix modelview_matrix; + ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data()); + Vec3d direction_to_camera(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]); + m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); + + if (m_quadric != nullptr && selection.is_from_single_instance()) + render_points(selection, direction_to_camera, false); + render_selection_rectangle(); + render_clipping_plane(selection, direction_to_camera); glsafe(::glDisable(GL_BLEND)); } + + +void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection, const Vec3d& direction_to_camera) const +{ + if (m_clipping_plane_distance == 0.f) + return; + + const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); + Transform3f instance_matrix = vol->get_instance_transformation().get_matrix().cast(); + Transform3f instance_matrix_no_translation_no_scaling = vol->get_instance_transformation().get_matrix(true,false,true).cast(); + Vec3f scaling = vol->get_instance_scaling_factor().cast(); + + Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * direction_to_camera.cast(); + Vec3f up = Vec3f(up_noscale(0)*scaling(0), up_noscale(1)*scaling(1), up_noscale(2)*scaling(2)); + float height_mesh = (m_active_instance_bb_radius - m_clipping_plane_distance * 2*m_active_instance_bb_radius) * (up_noscale.norm()/up.norm()); + + if (m_clipping_plane_distance != m_old_clipping_plane_distance + || m_old_direction_to_camera != direction_to_camera) { + + std::vector list_of_expolys; + if (! m_tms) { + m_tms.reset(new TriangleMeshSlicer); + m_tms->init(const_cast(&m_mesh), [](){}); + } + + m_tms->set_up_direction(up); + m_tms->slice(std::vector{height_mesh}, 0.f, &list_of_expolys, [](){}); + m_triangles = triangulate_expolygons_2f(list_of_expolys[0]); + + m_old_direction_to_camera = direction_to_camera; + m_old_clipping_plane_distance = m_clipping_plane_distance; + } + + if (! m_triangles.empty()) { + ::glPushMatrix(); + ::glTranslated(0.0, 0.0, m_z_shift); + ::glMultMatrixf(instance_matrix.data()); + Eigen::Quaternionf q; + q.setFromTwoVectors(Vec3f::UnitZ(), up); + Eigen::AngleAxisf aa(q); + ::glRotatef(aa.angle() * (180./M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2)); + ::glTranslatef(0.f, 0.f, -0.001f); // to make sure the cut is safely beyond the near clipping plane + ::glColor3f(1.0f, 0.37f, 0.0f); + ::glBegin(GL_TRIANGLES); + ::glColor3f(1.0f, 0.37f, 0.0f); + for (const Vec2f& point : m_triangles) + ::glVertex3f(point(0), point(1), height_mesh); + ::glEnd(); + ::glPopMatrix(); + } +} + + + void GLGizmoSlaSupports::render_selection_rectangle() const { if (m_selection_rectangle_status == srOff) @@ -143,24 +207,25 @@ void GLGizmoSlaSupports::on_render_for_picking(const Selection& selection) const { glsafe(::glEnable(GL_DEPTH_TEST)); - render_points(selection, true); + // we'll recover current look direction from the modelview matrix (in world coords): + Eigen::Matrix modelview_matrix; + ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data()); + Vec3d direction_to_camera(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]); + + render_points(selection, direction_to_camera, true); } -void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const +void GLGizmoSlaSupports::render_points(const Selection& selection, const Vec3d& direction_to_camera, bool picking) const { - if (m_quadric == nullptr || !selection.is_from_single_instance()) - return; - if (!picking) glsafe(::glEnable(GL_LIGHTING)); const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); - double z_shift = vol->get_sla_shift_z(); const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); const Transform3d& instance_matrix = vol->get_instance_transformation().get_matrix(); glsafe(::glPushMatrix()); - glsafe(::glTranslated(0.0, 0.0, z_shift)); + glsafe(::glTranslated(0.0, 0.0, m_z_shift)); glsafe(::glMultMatrixd(instance_matrix.data())); float render_color[3]; @@ -169,6 +234,9 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const sla::SupportPoint& support_point = m_editing_mode_cache[i].support_point; const bool& point_selected = m_editing_mode_cache[i].selected; + if (is_point_clipped(support_point.pos.cast(), direction_to_camera)) + continue; + // First decide about the color of the point. if (picking) { std::array color = picking_color_component(i); @@ -238,13 +306,25 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) glsafe(::glPopMatrix()); } + + +bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point, const Vec3d& direction_to_camera) const +{ + if (m_clipping_plane_distance == 0.f) + return false; + + Vec3d transformed_point = m_model_object->instances.front()->get_transformation().get_matrix() * point; + transformed_point(2) += m_z_shift; + return direction_to_camera.dot(m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift)) + m_active_instance_bb_radius + - m_clipping_plane_distance * 2*m_active_instance_bb_radius < direction_to_camera.dot(transformed_point); +} + + + bool GLGizmoSlaSupports::is_mesh_update_necessary() const { return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty()) - && ((m_model_object != m_old_model_object) || m_V.size()==0); - - //if (m_state != On || !m_model_object || m_model_object->instances.empty() || ! m_instance_matrix.isApprox(m_source_data.matrix)) - // return false; + && ((m_model_object->id() != m_current_mesh_model_id) || m_V.size()==0); } void GLGizmoSlaSupports::update_mesh() @@ -252,10 +332,9 @@ void GLGizmoSlaSupports::update_mesh() wxBusyCursor wait; Eigen::MatrixXf& V = m_V; Eigen::MatrixXi& F = m_F; - // Composite mesh of all instances in the world coordinate system. // This mesh does not account for the possible Z up SLA offset. - TriangleMesh mesh = m_model_object->raw_mesh(); - const stl_file& stl = mesh.stl; + m_mesh = m_model_object->raw_mesh(); + const stl_file& stl = m_mesh.stl; V.resize(3 * stl.stats.number_of_facets, 3); F.resize(stl.stats.number_of_facets, 3); for (unsigned int i=0; iid(); + m_editing_mode = false; m_AABB = igl::AABB(); m_AABB.init(m_V, m_F); } +// Unprojects the mouse position on the mesh and return the hit point and normal of the facet. +// The function throws if no intersection if found. std::pair GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) { // if the gizmo doesn't have the V, F structures for igl, calculate them first: @@ -288,31 +371,53 @@ std::pair GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse ::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 0.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point1(0), &point1(1), &point1(2)); ::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 1.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point2(0), &point2(1), &point2(2)); - igl::Hit hit; + std::vector hits; const Selection& selection = m_parent.get_selection(); const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - double z_offset = volume->get_sla_shift_z(); - point1(2) -= z_offset; - point2(2) -= z_offset; + // we'll recover current look direction from the modelview matrix (in world coords): + Vec3d direction_to_camera(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]); + + point1(2) -= m_z_shift; + point2(2) -= m_z_shift; Transform3d inv = volume->get_instance_transformation().get_matrix().inverse(); point1 = inv * point1; point2 = inv * point2; - if (!m_AABB.intersect_ray(m_V, m_F, point1.cast(), (point2-point1).cast(), hit)) + if (!m_AABB.intersect_ray(m_V, m_F, point1.cast(), (point2-point1).cast(), hits)) throw std::invalid_argument("unproject_on_mesh(): No intersection found."); - int fid = hit.id; // facet id - Vec3f bc(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit - Vec3f a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0))); - Vec3f b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0))); + std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; }); + + // Now let's iterate through the points and find the first that is not clipped: + unsigned int i=0; + Vec3f bc; + Vec3f a; + Vec3f b; + Vec3f result; + for (i=0; i(), direction_to_camera)) + break; + } + + if (i==hits.size() || (hits.size()-i) % 2 != 0) { + // All hits are either clipped, or there is an odd number of unclipped + // hits - meaning the nearest must be from inside the mesh. + throw std::invalid_argument("unproject_on_mesh(): No intersection found."); + } // Calculate and return both the point and the facet normal. return std::make_pair( - bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)), + result, a.cross(b) ); } @@ -383,36 +488,64 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const Selection& selection = m_parent.get_selection(); const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - double z_offset = volume->get_sla_shift_z(); // 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 = volume->get_instance_transformation().get_matrix(true); + 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. - direction_to_camera = (instance_matrix_no_translation.inverse().cast() * direction_to_camera).normalized().eval(); + 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) += z_offset; + 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))) { + if (rectangle.contains(Point(out_x, out_y)) && !is_point_clipped(support_point.pos.cast(), direction_to_camera.cast())) { bool is_obscured = false; // Cast a ray in the direction of the camera and look for intersection with the mesh: std::vector hits; // Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies. - if (m_AABB.intersect_ray(m_V, m_F, support_point.pos + direction_to_camera * (support_point.head_front_radius + EPSILON), direction_to_camera, hits)) + if (m_AABB.intersect_ray(m_V, m_F, support_point.pos + direction_to_camera_mesh * (support_point.head_front_radius + EPSILON), direction_to_camera_mesh, hits)) { + std::sort(hits.begin(), hits.end(), [](const igl::Hit& h1, const igl::Hit& h2) { return h1.t < h2.t; }); + + if (m_clipping_plane_distance != 0.f) { + // If the closest hit facet normal points in the same direction as the ray, + // we are looking through the mesh and should therefore discard the point: + int fid = hits.front().id; // facet id + Vec3f a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0))); + Vec3f b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0))); + if ((a.cross(b)).dot(direction_to_camera_mesh) > 0.f) + is_obscured = true; + + // Eradicate all hits that are on clipped surfaces: + for (unsigned int j=0; j(), direction_to_camera.cast())) { + hits.erase(hits.begin()+j); + --j; + } + } + } + // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction. // Also, the threshold is in mesh coordinates, not in actual dimensions. - if (hits.size() > 1 || hits.front().t > 0.001f) + if (!hits.empty()) is_obscured = true; + } if (!is_obscured) { if (m_selection_rectangle_status == srDeselect) @@ -570,6 +703,64 @@ void GLGizmoSlaSupports::update_cache_entry_normal(unsigned int i) const + +ClippingPlane GLGizmoSlaSupports::get_sla_clipping_plane() const +{ + if (!m_model_object || m_state == Off) + return ClippingPlane::ClipsNothing(); + + Eigen::Matrix modelview_matrix; + ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data()); + + // we'll recover current look direction from the modelview matrix (in world coords): + Vec3d direction_to_camera(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]); + float dist = direction_to_camera.dot(m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift)); + + return ClippingPlane(-direction_to_camera.normalized(),(dist - (-m_active_instance_bb_radius) - m_clipping_plane_distance * 2*m_active_instance_bb_radius)); +} + + +/* +void GLGizmoSlaSupports::find_intersecting_facets(const igl::AABB* aabb, const Vec3f& normal, double offset, std::vector& idxs) const +{ + if (aabb->is_leaf()) { // this is a facet + // corner.dot(normal) - offset + idxs.push_back(aabb->m_primitive); + } + else { // not a leaf + using CornerType = Eigen::AlignedBox::CornerType; + bool sign = std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(0)))); + for (unsigned int i=1; i<8; ++i) + if (std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(i)))) != sign) { + find_intersecting_facets(aabb->m_left, normal, offset, idxs); + find_intersecting_facets(aabb->m_right, normal, offset, idxs); + } + } +} + + + +void GLGizmoSlaSupports::make_line_segments() const +{ + TriangleMeshSlicer tms(&m_model_object->volumes.front()->mesh); + Vec3f normal(0.f, 1.f, 1.f); + double d = 0.; + + std::vector lines; + find_intersections(&m_AABB, normal, d, lines); + ExPolygons expolys; + tms.make_expolygons_simple(lines, &expolys); + + SVG svg("slice_loops.svg", get_extents(expolys)); + svg.draw(expolys); + //for (const IntersectionLine &l : lines[i]) + // svg.draw(l, "red", 0); + //svg.draw_outline(expolygons, "black", "blue", 0); + svg.Close(); +} +*/ + + void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) { if (!m_model_object) @@ -580,7 +771,7 @@ void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_l RENDER_AGAIN: m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - const ImVec2 window_size(m_imgui->scaled(17.f, 18.f)); + const ImVec2 window_size(m_imgui->scaled(17.f, 20.f)); ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) )); ImGui::SetNextWindowSize(ImVec2(window_size)); @@ -687,6 +878,13 @@ RENDER_AGAIN: (m_model_object->sla_points_status == sla::PointsStatus::Generating ? "Generation in progress..." : "UNKNOWN STATUS")))); } + + // Following is rendered in both editing and non-editing mode: + m_imgui->text("Clipping of view: "); + ImGui::SameLine(); + ImGui::PushItemWidth(150.0f); + bool value_changed = ImGui::SliderFloat(" ", &m_clipping_plane_distance, 0.f, 1.f, "%.2f"); + m_imgui->end(); if (m_editing_mode != m_old_editing_state) { // user toggled between editing/non-editing mode @@ -740,12 +938,7 @@ std::string GLGizmoSlaSupports::on_get_name() const void GLGizmoSlaSupports::on_set_state() { - // Following is called through CallAfter, because otherwise there was a problem - // on OSX with the wxMessageDialog being shown several times when clicked into. - - wxGetApp().CallAfter([this]() { if (m_state == On && m_old_state != On) { // the gizmo was just turned on - if (is_mesh_update_necessary()) update_mesh(); @@ -762,23 +955,32 @@ void GLGizmoSlaSupports::on_set_state() m_new_point_head_diameter = static_cast(cfg.option("support_head_front_diameter"))->value; } if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off - if (m_model_object) { - if (m_unsaved_changes) { - wxMessageDialog dlg(GUI::wxGetApp().mainframe, _(L("Do you want to save your manually edited support points ?\n")), - _(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); - if (dlg.ShowModal() == wxID_YES) - editing_mode_apply_changes(); - else - editing_mode_discard_changes(); + wxGetApp().CallAfter([this]() { + // Following is called through CallAfter, because otherwise there was a problem + // on OSX with the wxMessageDialog being shown several times when clicked into. + if (m_model_object) { + if (m_unsaved_changes) { + wxMessageDialog dlg(GUI::wxGetApp().mainframe, _(L("Do you want to save your manually edited support points ?\n")), + _(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); + if (dlg.ShowModal() == wxID_YES) + editing_mode_apply_changes(); + else + editing_mode_discard_changes(); + } } - } - - m_parent.toggle_model_objects_visibility(true); - m_editing_mode = false; // so it is not active next time the gizmo opens - m_editing_mode_cache.clear(); + m_parent.toggle_model_objects_visibility(true); + m_editing_mode = false; // so it is not active next time the gizmo opens + m_editing_mode_cache.clear(); + m_clipping_plane_distance = 0.f; + // Release copy of the mesh, triangle slicer and the AABB spatial search structure. + m_mesh.clear(); + m_AABB.deinit(); + m_V = Eigen::MatrixXf(); + m_F = Eigen::MatrixXi(); + m_tms.reset(nullptr); + }); } m_old_state = m_state; - }); } @@ -827,11 +1029,19 @@ void GLGizmoSlaSupports::unselect_point(int i) void GLGizmoSlaSupports::editing_mode_discard_changes() { - m_editing_mode_cache.clear(); - for (const sla::SupportPoint& point : m_model_object->sla_support_points) - m_editing_mode_cache.emplace_back(point, false); - m_editing_mode = false; - m_unsaved_changes = false; + // If the points were autogenerated, they may not be on the ModelObject yet. + // Because the user probably messed with the cache, we will get the data + // from the backend again. + + if (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated) + get_data_from_backend(); + else { + m_editing_mode_cache.clear(); + for (const sla::SupportPoint& point : m_model_object->sla_support_points) + m_editing_mode_cache.emplace_back(point, false); + } + m_editing_mode = false; + m_unsaved_changes = false; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index c8abf15f22..c74559e2fc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -17,12 +17,17 @@ namespace Slic3r { namespace GUI { +class ClippingPlane; + + class GLGizmoSlaSupports : public GLGizmoBase { private: ModelObject* m_model_object = nullptr; - ModelObject* m_old_model_object = nullptr; + ModelID m_current_mesh_model_id = 0; int m_active_instance = -1; + float m_active_instance_bb_radius; // to cache the bb + mutable float m_z_shift = 0.f; std::pair unproject_on_mesh(const Vec2d& mouse_pos); const float RenderPointScale = 1.f; @@ -31,6 +36,8 @@ private: Eigen::MatrixXf m_V; // vertices Eigen::MatrixXi m_F; // facets indices igl::AABB m_AABB; + TriangleMesh m_mesh; + mutable std::vector m_triangles; class CacheEntry { public: @@ -52,7 +59,7 @@ public: void set_sla_support_data(ModelObject* model_object, const Selection& selection); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); void delete_selected_points(bool force = false); - std::pair get_sla_clipping_plane() const; + ClippingPlane get_sla_clipping_plane() const; private: bool on_init(); @@ -61,7 +68,8 @@ private: virtual void on_render_for_picking(const Selection& selection) const; void render_selection_rectangle() const; - void render_points(const Selection& selection, bool picking = false) const; + void render_points(const Selection& selection, const Vec3d& direction_to_camera, bool picking = false) const; + void render_clipping_plane(const Selection& selection, const Vec3d& direction_to_camera) const; bool is_mesh_update_necessary() const; void update_mesh(); void update_cache_entry_normal(unsigned int i) const; @@ -74,6 +82,8 @@ private: float m_density = 100.f; mutable std::vector m_editing_mode_cache; // a support point and whether it is currently selected float m_clipping_plane_distance = 0.f; + mutable float m_old_clipping_plane_distance = 0.f; + mutable Vec3d m_old_direction_to_camera; enum SelectionRectangleStatus { srOff = 0, @@ -90,7 +100,11 @@ private: int m_canvas_width; int m_canvas_height; + mutable std::unique_ptr m_tms; + std::vector get_config_options(const std::vector& keys) const; + bool is_point_clipped(const Vec3d& point, const Vec3d& direction_to_camera) const; + void find_intersecting_facets(const igl::AABB* aabb, const Vec3f& normal, double offset, std::vector& out) const; // Methods that do the model_object and editing cache synchronization, // editing mode selection, etc: diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index ad2d786ffc..cb2d6faedf 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -467,6 +467,19 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p return false; } +ClippingPlane GLGizmosManager::get_sla_clipping_plane() const +{ + if (!m_enabled || m_current != SlaSupports) + return ClippingPlane::ClipsNothing(); + + GizmosMap::const_iterator it = m_gizmos.find(SlaSupports); + if (it != m_gizmos.end()) + return reinterpret_cast(it->second)->get_sla_clipping_plane(); + + return ClippingPlane::ClipsNothing(); +} + + void GLGizmosManager::render_current_gizmo(const Selection& selection) const { if (!m_enabled) @@ -713,7 +726,12 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt, GLCanvas3D& canvas) { switch (keyCode) { +#ifdef __APPLE__ + case 'a': + case 'A': +#else /* __APPLE__ */ case WXK_CONTROL_A: +#endif /* __APPLE__ */ { // Sla gizmo selects all support points if ((m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::SelectAll)) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 16e9025225..f7a1a980e6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -13,6 +13,7 @@ namespace GUI { class Selection; class GLGizmoBase; class GLCanvas3D; +class ClippingPlane; class Rect { @@ -146,7 +147,7 @@ public: void set_sla_support_data(ModelObject* model_object, const Selection& selection); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false, bool alt_down = false, bool control_down = false); - + ClippingPlane get_sla_clipping_plane() const; void render_current_gizmo(const Selection& selection) const; void render_current_gizmo_for_picking_pass(const Selection& selection) const; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 5f34343905..fc49514732 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -321,7 +321,7 @@ 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(); }, "brick_add.png"); + [this](wxCommandEvent&) { if (m_plater) m_plater->load_project(); }, "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"); wxMenuItem* item_save_as = append_menu_item(fileMenu, wxID_ANY, _(L("Save Project &as")) + dots + "\tCtrl+Alt+S", _(L("Save current project file as")), @@ -331,30 +331,30 @@ void MainFrame::init_menubar() 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(); }, "brick_add.png"); + [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, "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(); }, "plugin_add.png"); + [this](wxCommandEvent&) { load_config_file(); }, "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(); }, "plugin_add.png"); + [this](wxCommandEvent&) { if (m_plater) m_plater->extract_config_from_project(); }, "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(); }, "lorry_add.png"); + [this](wxCommandEvent&) { load_configbundle(); }, "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(); }, "cog_go.png"); + [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(); }, "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(); }, "brick_go.png"); + [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(); }, "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(); }, "brick_go.png"); + [this](wxCommandEvent&) { if (m_plater) m_plater->export_amf(); }, "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(); }, "plugin_go.png"); + [this](wxCommandEvent&) { export_config(); }, "export_config"); append_menu_item(export_menu, wxID_ANY, _(L("Export Config &Bundle")) + dots, _(L("Export all presets to file")), - [this](wxCommandEvent&) { export_configbundle(); }, "lorry_go.png"); + [this](wxCommandEvent&) { export_configbundle(); }, "export_config_bundle"); append_submenu(fileMenu, export_menu, wxID_ANY, _(L("&Export")), ""); fileMenu->AppendSeparator(); @@ -382,10 +382,10 @@ 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(); }, "shape_handles.png"); + [this](wxCommandEvent&) { reslice_now(); }, "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.png"); + [this](wxCommandEvent&) { repair_stl(); }, "wrench"); fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_EXIT, _(L("&Quit")), _(L("Quit Slic3r")), [this](wxCommandEvent&) { Close(false); }); @@ -425,13 +425,22 @@ void MainFrame::init_menubar() [this](wxCommandEvent&) { 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(); }, ""); + [this](wxCommandEvent&) { m_plater->remove_selected(); }, "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(); }, ""); + [this](wxCommandEvent&) { m_plater->reset(); }, "delete_all_menu"); + + editMenu->AppendSeparator(); + + wxMenuItem* item_copy = append_menu_item(editMenu, wxID_ANY, _(L("&Copy")) + "\tCtrl+C", _(L("Copy selection to clipboard")), + [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); }, "copy_menu"); + wxMenuItem* item_paste = append_menu_item(editMenu, wxID_ANY, _(L("&Paste")) + "\tCtrl+V", _(L("Paste clipboard")), + [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); }, "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()); Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete_all()); }, item_delete_all->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_plater->can_copy()); }, item_copy->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_plater->can_paste()); }, item_paste->GetId()); } // Window menu @@ -440,7 +449,7 @@ 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); }, "application_view_tile.png"); + [this](wxCommandEvent&) { select_tab(0); }, "plater"); tab_offset += 1; } if (tab_offset > 0) { @@ -455,9 +464,9 @@ void MainFrame::init_menubar() 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"); }, ""); + [this](wxCommandEvent&) { m_plater->select_view_3D("3D"); }, "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"); }, ""); + [this](wxCommandEvent&) { m_plater->select_view_3D("Preview"); }, "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()); @@ -478,7 +487,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(); }, "arrow_up.png"); + [this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "upload_queue"); } // View menu @@ -590,7 +599,7 @@ void MainFrame::quick_slice(const int qs) dlg->ShowModal(); return; } - if (std::ifstream(m_qs_last_input_file.char_str())) { + if (std::ifstream(m_qs_last_input_file.ToUTF8().data())) { auto dlg = new wxMessageDialog(this, _(L("Previously sliced file ("))+m_qs_last_input_file+_(L(") not found.")), _(L("File Not Found")), wxICON_ERROR | wxOK); dlg->ShowModal(); @@ -705,24 +714,23 @@ void MainFrame::repair_stl() dlg->Destroy(); } - auto output_file = input_file; + wxString output_file = input_file; { -// output_file = ~s / \.[sS][tT][lL]$ / _fixed.obj / ; auto dlg = new wxFileDialog( this, L("Save OBJ file (less prone to coordinate errors than STL) as:"), - get_dir_name(output_file), get_base_name(output_file), + get_dir_name(output_file), get_base_name(output_file, ".obj"), file_wildcards(FT_OBJ), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (dlg->ShowModal() != wxID_OK) { dlg->Destroy(); - return /*undef*/; + return; } output_file = dlg->GetPath(); dlg->Destroy(); } auto tmesh = new Slic3r::TriangleMesh(); - tmesh->ReadSTLFile(input_file.char_str()); + tmesh->ReadSTLFile(input_file.ToUTF8().data()); tmesh->repair(); - tmesh->WriteOBJFile(output_file.char_str()); + tmesh->WriteOBJFile(output_file.ToUTF8().data()); Slic3r::GUI::show_info(this, L("Your file was repaired."), L("Repair")); } @@ -962,9 +970,12 @@ void MainFrame::update_ui_from_settings() tab->update_ui_from_settings(); } -std::string MainFrame::get_base_name(const wxString &full_name) const +std::string MainFrame::get_base_name(const wxString &full_name, const char *extension) const { - return boost::filesystem::path(full_name.wx_str()).filename().string(); + boost::filesystem::path filename = boost::filesystem::path(full_name.wx_str()).filename(); + if (extension != nullptr) + filename = filename.replace_extension(extension); + return filename.string(); } std::string MainFrame::get_dir_name(const wxString &full_name) const diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 625e70b83b..a5d3a1f6de 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -54,7 +54,7 @@ class MainFrame : public DPIFrame PrintHostQueueDialog *m_printhost_queue_dlg; - std::string get_base_name(const wxString &full_name) const; + std::string get_base_name(const wxString &full_name, const char *extension = nullptr) const; std::string get_dir_name(const wxString &full_name) const; void on_presets_changed(SimpleEvent&); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 5b7ac86fa0..2224eff5e3 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -144,8 +144,7 @@ ObjectInfo::ObjectInfo(wxWindow *parent) : info_manifold_text->SetFont(wxGetApp().small_font()); info_manifold = new wxStaticText(parent, wxID_ANY, ""); info_manifold->SetFont(wxGetApp().small_font()); - wxBitmap bitmap(GUI::from_u8(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG); - manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, bitmap); + manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap(parent, "exclamation")); auto *sizer_manifold = new wxBoxSizer(wxHORIZONTAL); sizer_manifold->Add(info_manifold_text, 0); sizer_manifold->Add(manifold_warning_icon, 0, wxLEFT, 2); @@ -1477,6 +1476,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) 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_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(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_FEWER, [q](SimpleEvent&) { q->decrease_instances(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_OBJECTS, &priv::on_action_split_objects, this); @@ -1719,11 +1720,11 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (advanced) { - wxMessageDialog dlg(q, _(L("This file cannot be loaded in simple mode. Do you want to switch to expert mode?\n")), + wxMessageDialog dlg(q, _(L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?\n")), _(L("Detected advanced data")), wxICON_WARNING | wxYES | wxNO); if (dlg.ShowModal() == wxID_YES) { - Slic3r::GUI::wxGetApp().save_mode(comExpert); + Slic3r::GUI::wxGetApp().save_mode(comAdvanced); view3D->set_as_dirty(); } else @@ -2669,25 +2670,21 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) this->statusbar()->set_progress(evt.status.percent); this->statusbar()->set_status_text(_(L(evt.status.text)) + wxString::FromUTF8("…")); } - if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SCENE) { + if (evt.status.flags & (PrintBase::SlicingStatus::RELOAD_SCENE || PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS)) { switch (this->printer_technology) { case ptFFF: this->update_fff_scene(); break; case ptSLA: + // If RELOAD_SLA_SUPPORT_POINTS, then the SLA gizmo is updated (reload_scene calls update_gizmos_data) if (view3D->is_dragging()) delayed_scene_refresh = true; else this->update_sla_scene(); break; } - } - if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS) { - // Update SLA gizmo (reload_scene calls update_gizmos_data) - q->canvas3D()->reload_scene(true); - } - if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_PREVIEW) { - // Update the SLA preview + } else if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_PREVIEW) { + // Update the SLA preview. Only called if not RELOAD_SLA_SUPPORT_POINTS, as the block above will refresh the preview anyways. this->preview->reload_print(); } } @@ -2895,17 +2892,17 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ wxMenuItem* item_delete = nullptr; if (is_part) { item_delete = append_menu_item(menu, wxID_ANY, _(L("Delete")) + "\tDel", _(L("Remove the selected object")), - [this](wxCommandEvent&) { q->remove_selected(); }, "remove"); + [this](wxCommandEvent&) { q->remove_selected(); }, "delete"); sidebar->obj_list()->append_menu_item_export_stl(menu); } else { wxMenuItem* item_increase = append_menu_item(menu, wxID_ANY, _(L("Increase copies")) + "\t+", _(L("Place one more copy of the selected object")), - [this](wxCommandEvent&) { q->increase_instances(); }, "instance_add"); + [this](wxCommandEvent&) { q->increase_instances(); }, "add_copies"); wxMenuItem* item_decrease = append_menu_item(menu, wxID_ANY, _(L("Decrease copies")) + "\t-", _(L("Remove one copy of the selected object")), - [this](wxCommandEvent&) { q->decrease_instances(); }, "instance_remove"); + [this](wxCommandEvent&) { q->decrease_instances(); }, "remove_copies"); wxMenuItem* item_set_number_of_copies = append_menu_item(menu, wxID_ANY, _(L("Set number of copies")) + dots, _(L("Change the number of copies of the selected object")), - [this](wxCommandEvent&) { q->set_number_of_copies(); }, "textfield.png"); + [this](wxCommandEvent&) { q->set_number_of_copies(); }, "number_of_copies"); items_increase.push_back(item_increase); items_decrease.push_back(item_decrease); @@ -2913,7 +2910,7 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ // Delete menu was moved to be after +/- instace to make it more difficult to be selected by mistake. item_delete = append_menu_item(menu, wxID_ANY, _(L("Delete")) + "\tDel", _(L("Remove the selected object")), - [this](wxCommandEvent&) { q->remove_selected(); }, "remove"); + [this](wxCommandEvent&) { q->remove_selected(); }, "delete"); menu->AppendSeparator(); wxMenuItem* item_instance_to_object = sidebar->obj_list()->append_menu_item_instance_to_object(menu); @@ -2943,11 +2940,11 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ return false; append_menu_item(mirror_menu, wxID_ANY, _(L("Along X axis")), _(L("Mirror the selected object along the X axis")), - [this](wxCommandEvent&) { mirror(X); }, "bullet_red.png", menu); + [this](wxCommandEvent&) { mirror(X); }, "mark_X", menu); append_menu_item(mirror_menu, wxID_ANY, _(L("Along Y axis")), _(L("Mirror the selected object along the Y axis")), - [this](wxCommandEvent&) { mirror(Y); }, "bullet_green.png", menu); + [this](wxCommandEvent&) { mirror(Y); }, "mark_Y", menu); append_menu_item(mirror_menu, wxID_ANY, _(L("Along Z axis")), _(L("Mirror the selected object along the Z axis")), - [this](wxCommandEvent&) { mirror(Z); }, "bullet_blue.png", menu); + [this](wxCommandEvent&) { mirror(Z); }, "mark_Z", menu); wxMenuItem* item_mirror = append_submenu(menu, mirror_menu, wxID_ANY, _(L("Mirror")), _(L("Mirror the selected object"))); @@ -2968,9 +2965,9 @@ bool Plater::priv::complit_init_object_menu() return false; wxMenuItem* item_split_objects = append_menu_item(split_menu, wxID_ANY, _(L("To objects")), _(L("Split the selected object into individual objects")), - [this](wxCommandEvent&) { split_object(); }, "split_objects.png", &object_menu); + [this](wxCommandEvent&) { split_object(); }, "split_object_SMALL", &object_menu); wxMenuItem* item_split_volumes = append_menu_item(split_menu, wxID_ANY, _(L("To parts")), _(L("Split the selected object into individual sub-parts")), - [this](wxCommandEvent&) { split_volume(); }, "split_parts.png", &object_menu); + [this](wxCommandEvent&) { split_volume(); }, "split_parts_SMALL", &object_menu); wxMenuItem* item_split = append_submenu(&object_menu, split_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object"))/*, "shape_ungroup.png"*/); object_menu.AppendSeparator(); @@ -2990,7 +2987,7 @@ bool Plater::priv::complit_init_object_menu() bool Plater::priv::complit_init_sla_object_menu() { wxMenuItem* item_split = append_menu_item(&sla_object_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object into individual objects")), - [this](wxCommandEvent&) { split_object(); }, "shape_ungroup_o.png"); + [this](wxCommandEvent&) { split_object(); }, "split_object_SMALL"); sla_object_menu.AppendSeparator(); @@ -3010,7 +3007,7 @@ bool Plater::priv::complit_init_sla_object_menu() bool Plater::priv::complit_init_part_menu() { wxMenuItem* item_split = append_menu_item(&part_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object into individual sub-parts")), - [this](wxCommandEvent&) { split_volume(); }, "shape_ungroup_p.png"); + [this](wxCommandEvent&) { split_volume(); }, "split_parts_SMALL"); part_menu.AppendSeparator(); @@ -3117,7 +3114,7 @@ void Plater::priv::set_bed_shape(const Pointfs& shape) bool Plater::priv::can_delete() const { - return !get_selection().is_empty(); + return !get_selection().is_empty() && !get_selection().is_wipe_tower(); } bool Plater::priv::can_delete_all() const @@ -3299,8 +3296,9 @@ void Plater::increase_instances(size_t num) bool was_one_instance = model_object->instances.size()==1; - float offset = 10.0; - for (size_t i = 0; i < num; i++, offset += 10.0) { + double offset_base = canvas3D()->get_size_proportional_to_max_bed_size(0.05); + double offset = offset_base; + for (size_t i = 0; i < num; i++, offset += offset_base) { Vec3d offset_vec = model_instance->get_offset() + Vec3d(offset, offset, 0.0); model_object->add_instance(offset_vec, model_instance->get_scaling_factor(), model_instance->get_rotation(), model_instance->get_mirror()); // p->print.get_object(obj_idx)->add_copy(Slic3r::to_2d(offset_vec)); @@ -3368,7 +3366,7 @@ void Plater::set_number_of_copies(/*size_t num*/) bool Plater::is_selection_empty() const { - return p->get_selection().is_empty(); + return p->get_selection().is_empty() || p->get_selection().is_wipe_tower(); } void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper, bool keep_lower, bool rotate_lower) @@ -3771,10 +3769,43 @@ void Plater::changed_object(int obj_idx) this->p->schedule_background_process(); } +void Plater::schedule_background_process() +{ + this->p->schedule_background_process(); +} + void Plater::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/) { p->fix_through_netfabb(obj_idx, vol_idx); } void Plater::update_object_menu() { p->update_object_menu(); } +void Plater::copy_selection_to_clipboard() +{ + p->view3D->get_canvas3d()->get_selection().copy_to_clipboard(); +} + +void Plater::paste_from_clipboard() +{ + p->view3D->get_canvas3d()->get_selection().paste_from_clipboard(); +} + +bool Plater::can_paste_from_clipboard() const +{ + const Selection& selection = p->view3D->get_canvas3d()->get_selection(); + const Selection::Clipboard& clipboard = selection.get_clipboard(); + Selection::EMode mode = clipboard.get_mode(); + + if (clipboard.is_empty()) + return false; + + if ((mode == Selection::Volume) && !selection.is_from_single_instance()) + return false; + + if ((mode == Selection::Instance) && (selection.get_mode() != Selection::Instance)) + return false; + + return true; +} + bool Plater::can_delete() const { return p->can_delete(); } bool Plater::can_delete_all() const { return p->can_delete_all(); } bool Plater::can_increase_instances() const { return p->can_increase_instances(); } @@ -3783,5 +3814,7 @@ bool Plater::can_split_to_objects() const { return p->can_split_to_objects(); } bool Plater::can_split_to_volumes() const { return p->can_split_to_volumes(); } bool Plater::can_arrange() const { return p->can_arrange(); } bool Plater::can_layers_editing() const { return p->can_layers_editing(); } +bool Plater::can_copy() const { return !is_selection_empty(); } +bool Plater::can_paste() const { return can_paste_from_clipboard(); } }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index b2d904a2e3..418e56c84d 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -167,6 +167,7 @@ public: void reslice(); void reslice_SLA_supports(const ModelObject &object); void changed_object(int obj_idx); + void schedule_background_process(); void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); void send_gcode(); @@ -187,6 +188,10 @@ public: PrinterTechnology printer_technology() const; void set_printer_technology(PrinterTechnology printer_technology); + void copy_selection_to_clipboard(); + void paste_from_clipboard(); + bool can_paste_from_clipboard() const; + bool can_delete() const; bool can_delete_all() const; bool can_increase_instances() const; @@ -195,6 +200,8 @@ public: bool can_split_to_volumes() const; bool can_arrange() const; bool can_layers_editing() const; + bool can_copy() const; + bool can_paste() const; private: struct priv; diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index c573dd39da..01a74e15b1 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -801,12 +801,16 @@ bool PresetCollection::delete_current_preset() void PresetCollection::load_bitmap_default(wxWindow *window, const std::string &file_name) { - *m_bitmap_main_frame = create_scaled_bitmap(window, file_name); + // XXX: See note in PresetBundle::load_compatible_bitmaps() + (void)window; + *m_bitmap_main_frame = create_scaled_bitmap(nullptr, file_name); } void PresetCollection::load_bitmap_add(wxWindow *window, const std::string &file_name) { - *m_bitmap_add = create_scaled_bitmap(window, file_name); + // XXX: See note in PresetBundle::load_compatible_bitmaps() + (void)window; + *m_bitmap_add = create_scaled_bitmap(nullptr, file_name); } const Preset* PresetCollection::get_selected_preset_parent() const diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index 0a84055490..7927006b25 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -398,22 +398,27 @@ void PresetBundle::export_selections(AppConfig &config) void PresetBundle::load_compatible_bitmaps(wxWindow *window) { - *m_bitmapCompatible = create_scaled_bitmap(window, "flag_green"); - *m_bitmapIncompatible = create_scaled_bitmap(window, "flag_red"); - *m_bitmapLock = create_scaled_bitmap(window, "lock_closed"); - *m_bitmapLockOpen = create_scaled_bitmap(window, "sys_unlock.png"); + // We don't actually pass the window pointer here and instead generate + // a low DPI bitmap, because the wxBitmapComboBox and wxDataViewCtrl don't support + // high DPI bitmaps very well, they compute their dimensions wrong. + // TODO: Update this when fixed in wxWidgets + // See also PresetCollection::load_bitmap_default() and PresetCollection::load_bitmap_add() + + (void)window; + *m_bitmapCompatible = create_scaled_bitmap(nullptr, "flag_green"); + *m_bitmapIncompatible = create_scaled_bitmap(nullptr, "flag_red"); + *m_bitmapLock = create_scaled_bitmap(nullptr, "lock_closed"); + *m_bitmapLockOpen = create_scaled_bitmap(nullptr, "sys_unlock.png"); prints .set_bitmap_compatible(m_bitmapCompatible); filaments .set_bitmap_compatible(m_bitmapCompatible); sla_prints .set_bitmap_compatible(m_bitmapCompatible); sla_materials.set_bitmap_compatible(m_bitmapCompatible); - printers .set_bitmap_compatible(m_bitmapCompatible); prints .set_bitmap_incompatible(m_bitmapIncompatible); filaments .set_bitmap_incompatible(m_bitmapIncompatible); sla_prints .set_bitmap_incompatible(m_bitmapIncompatible); sla_materials.set_bitmap_incompatible(m_bitmapIncompatible); - printers .set_bitmap_incompatible(m_bitmapIncompatible); prints .set_bitmap_lock(m_bitmapLock); filaments .set_bitmap_lock(m_bitmapLock); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index dedf55a45a..957c04d690 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1022,6 +1022,77 @@ bool Selection::requires_local_axes() const return (m_mode == Volume) && is_from_single_instance(); } +void Selection::copy_to_clipboard() +{ + if (!m_valid) + return; + + m_clipboard.reset(); + + for (const ObjectIdxsToInstanceIdxsMap::value_type& object : m_cache.content) + { + ModelObject* src_object = m_model->objects[object.first]; + ModelObject* dst_object = m_clipboard.add_object(); + dst_object->name = src_object->name; + dst_object->input_file = src_object->input_file; + dst_object->config = src_object->config; + dst_object->sla_support_points = src_object->sla_support_points; + dst_object->sla_points_status = src_object->sla_points_status; + dst_object->layer_height_ranges = src_object->layer_height_ranges; + dst_object->layer_height_profile = src_object->layer_height_profile; + dst_object->origin_translation = src_object->origin_translation; + + for (int i : object.second) + { + dst_object->add_instance(*src_object->instances[i]); + } + + for (unsigned int i : m_list) + { + // Copy the ModelVolumes only for the selected GLVolumes of the 1st selected instance. + const GLVolume* volume = (*m_volumes)[i]; + if ((volume->object_idx() == object.first) && (volume->instance_idx() == *object.second.begin())) + { + int volume_idx = volume->volume_idx(); + if ((0 <= volume_idx) && (volume_idx < (int)src_object->volumes.size())) + { + ModelVolume* src_volume = src_object->volumes[volume_idx]; + ModelVolume* dst_volume = dst_object->add_volume(*src_volume); + dst_volume->set_new_unique_id(); + } else { + assert(false); + } + } + } + } + + m_clipboard.set_mode(m_mode); +} + +void Selection::paste_from_clipboard() +{ + if (!m_valid || m_clipboard.is_empty()) + return; + + switch (m_clipboard.get_mode()) + { + case Volume: + { + if (is_from_single_instance()) + paste_volumes_from_clipboard(); + + break; + } + case Instance: + { + if (m_mode == Instance) + paste_objects_from_clipboard(); + + break; + } + } +} + void Selection::update_valid() { m_valid = (m_volumes != nullptr) && (m_model != nullptr); @@ -1697,5 +1768,44 @@ bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const return count == (unsigned int)m_model->objects[object_idx]->volumes.size(); } +void Selection::paste_volumes_from_clipboard() +{ + int obj_idx = get_object_idx(); + if ((obj_idx < 0) || ((int)m_model->objects.size() <= obj_idx)) + return; + + ModelObject* src_object = m_clipboard.get_object(0); + if (src_object != nullptr) + { + ModelObject* dst_object = m_model->objects[obj_idx]; + + ModelVolumePtrs volumes; + for (ModelVolume* src_volume : src_object->volumes) + { + ModelVolume* dst_volume = dst_object->add_volume(*src_volume); + dst_volume->set_new_unique_id(); + double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05); + dst_volume->translate(offset, offset, 0.0); + volumes.push_back(dst_volume); + } + wxGetApp().obj_list()->paste_volumes_into_list(obj_idx, volumes); + } +} + +void Selection::paste_objects_from_clipboard() +{ + std::vector object_idxs; + const ModelObjectPtrs& src_objects = m_clipboard.get_objects(); + for (const ModelObject* src_object : src_objects) + { + ModelObject* dst_object = m_model->add_object(*src_object); + double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05); + dst_object->translate(offset, offset, 0.0); + object_idxs.push_back(m_model->objects.size() - 1); + } + + wxGetApp().obj_list()->paste_objects_into_list(object_idxs); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index b03a8e89ac..a8b0c06dcb 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -138,6 +138,23 @@ public: typedef std::set InstanceIdxsList; typedef std::map ObjectIdxsToInstanceIdxsMap; + class Clipboard + { + Model m_model; + Selection::EMode m_mode; + + public: + void reset() { m_model.clear_objects(); } + bool is_empty() const { return m_model.objects.empty(); } + + ModelObject* add_object() { return m_model.add_object(); } + ModelObject* get_object(unsigned int id) { return (id < (unsigned int)m_model.objects.size()) ? m_model.objects[id] : nullptr; } + const ModelObjectPtrs& get_objects() const { return m_model.objects; } + + Selection::EMode get_mode() const { return m_mode; } + void set_mode(Selection::EMode mode) { m_mode = mode; } + }; + private: struct Cache { @@ -163,6 +180,7 @@ private: // set of indices to m_volumes IndicesList m_list; Cache m_cache; + Clipboard m_clipboard; mutable BoundingBoxf3 m_bounding_box; mutable bool m_bounding_box_dirty; @@ -267,6 +285,11 @@ public: bool requires_local_axes() const; + void copy_to_clipboard(); + void paste_from_clipboard(); + + const Clipboard& get_clipboard() const { return m_clipboard; } + private: void update_valid(); void update_type(); @@ -301,6 +324,9 @@ private: void synchronize_unselected_volumes(); void ensure_on_bed(); bool is_from_fully_selected_instance(unsigned int volume_idx) const; + + void paste_volumes_from_clipboard(); + void paste_objects_from_clipboard(); }; } // namespace GUI diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 2dd277d7ba..8a7ac0433d 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -165,10 +165,13 @@ void Tab::create_preset_tab() add_scaled_bitmap(this, m_bmp_value_unlock, "lock_open"); m_bmp_non_system = &m_bmp_white_bullet; // Bitmaps to be shown on the "Undo user changes" button next to each input field. +// m_bmp_value_revert = create_scaled_bitmap(this, "undo"); +// m_bmp_white_bullet = create_scaled_bitmap(this, luma >= 128 ? "dot" : "dot_white"/*"bullet_white.png"*/); +// m_bmp_question = create_scaled_bitmap(this, "question"); // m_bmp_value_revert = create_scaled_bitmap(this, "undo"); // m_bmp_white_bullet = create_scaled_bitmap(this, "bullet_white.png"); add_scaled_bitmap(this, m_bmp_value_revert, "undo"); - add_scaled_bitmap(this, m_bmp_white_bullet, "bullet_white.png"); + add_scaled_bitmap(this, m_bmp_white_bullet, luma >= 128 ? "dot" : "dot_white"/*"bullet_white.png"*/); fill_icon_descriptions(); set_tooltips_text(); @@ -1163,7 +1166,7 @@ void TabPrint::build() optgroup = page->new_optgroup(_(L("Advanced"))); optgroup->append_single_option_line("interface_shells"); - page = add_options_page(_(L("Advanced")), "wrench.png"); + page = add_options_page(_(L("Advanced")), "wrench"); optgroup = page->new_optgroup(_(L("Extrusion width"))); optgroup->append_single_option_line("extrusion_width"); optgroup->append_single_option_line("first_layer_extrusion_width"); @@ -1536,7 +1539,7 @@ void TabFilament::build() optgroup->append_single_option_line("slowdown_below_layer_time"); optgroup->append_single_option_line("min_print_speed"); - page = add_options_page(_(L("Advanced")), "wrench.png"); + page = add_options_page(_(L("Advanced")), "wrench"); optgroup = page->new_optgroup(_(L("Filament properties"))); optgroup->append_single_option_line("filament_type"); optgroup->append_single_option_line("filament_soluble"); @@ -1702,12 +1705,17 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) } auto printhost_browse = [=](wxWindow* parent) { +// auto btn = m_printhost_browse_btn = new wxButton(parent, wxID_ANY, _(L(" Browse ")) + dots, +// wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); +// btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); +// btn->SetBitmap(create_scaled_bitmap(this, "browse")); // auto btn = m_printhost_browse_btn = new wxButton(parent, wxID_ANY, _(L(" Browse ")) + dots, // wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); // btn->SetBitmap(create_scaled_bitmap(this, "zoom.png")); - add_scaled_button(parent, &m_printhost_browse_btn, "zoom.png", _(L(" Browse ")) + dots, wxBU_LEFT | wxBU_EXACTFIT); + add_scaled_button(parent, &m_printhost_browse_btn, "browse", _(L(" Browse ")) + dots, wxBU_LEFT | wxBU_EXACTFIT); PrusaButton* btn = m_printhost_browse_btn; - btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); @@ -1722,6 +1730,11 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) return sizer; }; + auto print_host_test = [this](wxWindow* parent) { + auto btn = m_print_host_test_btn = new wxButton(parent, wxID_ANY, _(L("Test")), + wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); + btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + btn->SetBitmap(create_scaled_bitmap(this, "test")); auto print_host_test = [this](wxWindow* parent) { // auto btn = m_print_host_test_btn = new wxButton(parent, wxID_ANY, _(L("Test")), // wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); @@ -1765,7 +1778,7 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) auto printhost_cafile_browse = [this, optgroup] (wxWindow* parent) { auto btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT); btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - btn->SetBitmap(create_scaled_bitmap(this, "zoom.png")); + btn->SetBitmap(create_scaled_bitmap(this, "browse")); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); @@ -3330,7 +3343,8 @@ void TabSLAMaterial::build() optgroup = page->new_optgroup(_(L("Corrections"))); optgroup->label_width = 19;//190; std::vector corrections = {"material_correction"}; - std::vector axes{ "X", "Y", "Z" }; +// std::vector axes{ "X", "Y", "Z" }; + std::vector axes{ "XY", "Z" }; for (auto& opt_key : corrections) { auto line = Line{ m_config->def()->get(opt_key)->full_label, "" }; int id = 0; @@ -3413,7 +3427,7 @@ void TabSLAPrint::build() optgroup->append_single_option_line("layer_height"); optgroup->append_single_option_line("faded_layers"); - page = add_options_page(_(L("Supports")), "sla_supports"); + page = add_options_page(_(L("Supports")), "support"/*"sla_supports"*/); optgroup = page->new_optgroup(_(L("Supports"))); optgroup->append_single_option_line("supports_enable"); @@ -3441,7 +3455,7 @@ void TabSLAPrint::build() optgroup->append_single_option_line("support_points_density_relative"); optgroup->append_single_option_line("support_points_minimal_distance"); - page = add_options_page(_(L("Pad")), "brick.png"); + page = add_options_page(_(L("Pad")), "pad"); optgroup = page->new_optgroup(_(L("Pad"))); optgroup->append_single_option_line("pad_enable"); optgroup->append_single_option_line("pad_wall_thickness"); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index c98c45295d..4479ef70e8 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -461,10 +461,10 @@ wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in, con // ---------------------------------------------------------------------------- void PrusaObjectDataViewModelNode::set_object_action_icon() { - m_action_icon = create_scaled_bitmap(nullptr, "add_object.png"); // FIXME: pass window ptr + m_action_icon = create_scaled_bitmap(nullptr, "advanced_plus"); // FIXME: pass window ptr } void PrusaObjectDataViewModelNode::set_part_action_icon() { - m_action_icon = create_scaled_bitmap(nullptr, m_type == itVolume ? "cog.png" : "brick_go.png"); // FIXME: pass window ptr + m_action_icon = create_scaled_bitmap(nullptr, m_type == itVolume ? "cog" : "set_separate_obj"); // FIXME: pass window ptr } Slic3r::GUI::BitmapCache *m_bitmap_cache = nullptr; @@ -529,10 +529,10 @@ wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name, const int ext } wxDataViewItem PrusaObjectDataViewModel::AddVolumeChild(const wxDataViewItem &parent_item, - const wxString &name, - const Slic3r::ModelVolumeType volume_type, - const int extruder/* = 0*/, - const bool create_frst_child/* = true*/) + const wxString &name, + const Slic3r::ModelVolumeType volume_type, + const int extruder/* = 0*/, + const bool create_frst_child/* = true*/) { PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent_item.GetID(); if (!root) return wxDataViewItem(0); @@ -545,7 +545,7 @@ wxDataViewItem PrusaObjectDataViewModel::AddVolumeChild(const wxDataViewItem &pa insert_position = -1; if (create_frst_child && root->m_volumes_cnt == 0) - { + { const auto node = new PrusaObjectDataViewModelNode(root, root->m_name, *m_volume_bmps[0], extruder_str, 0); insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); // notify control diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index bd45103c17..597b4fb947 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -454,12 +454,12 @@ public: ~PrusaObjectDataViewModel(); wxDataViewItem Add(const wxString &name, const int extruder); - wxDataViewItem AddVolumeChild(const wxDataViewItem &parent_item, - const wxString &name, - const Slic3r::ModelVolumeType volume_type, - const int extruder = 0, - const bool create_frst_child = true); - wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); + wxDataViewItem AddVolumeChild(const wxDataViewItem &parent_item, + const wxString &name, + const Slic3r::ModelVolumeType volume_type, + const int extruder = 0, + const bool create_frst_child = true); + wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); wxDataViewItem Delete(const wxDataViewItem &item); wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num); diff --git a/src/slic3r_app_msvc.cpp b/src/slic3r_app_msvc.cpp index 380d30cf40..c100f8fd9b 100644 --- a/src/slic3r_app_msvc.cpp +++ b/src/slic3r_app_msvc.cpp @@ -6,14 +6,20 @@ #include #include #include -// Let the NVIDIA and AMD know we want to use their graphics card -// on a dual graphics card system. -__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; -__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; + +#ifdef SLIC3R_GUI + // Let the NVIDIA and AMD know we want to use their graphics card + // on a dual graphics card system. + __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; + __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; +#endif /* SLIC3R_GUI */ #include #include -#include + +#ifdef SLIC3R_GUI + #include +#endif /* SLIC3R_GUI */ #include #include @@ -23,6 +29,7 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; #include +#ifdef SLIC3R_GUI class OpenGLVersionCheck { public: @@ -188,6 +195,7 @@ protected: }; bool OpenGLVersionCheck::message_pump_exit = false; +#endif /* SLIC3R_GUI */ extern "C" { typedef int (__stdcall *Slic3rMainFunc)(int argc, wchar_t **argv); @@ -206,18 +214,33 @@ int wmain(int argc, wchar_t **argv) std::vector argv_extended; argv_extended.emplace_back(argv[0]); + +#ifdef SLIC3R_GUI // Here one may push some additional parameters based on the wrapper type. - for (int i = 1; i < argc; ++ i) + bool force_mesa = false; +#endif /* SLIC3R_GUI */ + for (int i = 1; i < argc; ++ i) { +#ifdef SLIC3R_GUI + if (wcscmp(argv[i], L"--sw-renderer") == 0) + force_mesa = true; + else if (wcscmp(argv[i], L"--no-sw-renderer") == 0) + force_mesa = false; +#endif /* SLIC3R_GUI */ argv_extended.emplace_back(argv[i]); + } argv_extended.emplace_back(nullptr); +#ifdef SLIC3R_GUI OpenGLVersionCheck opengl_version_check; bool load_mesa = + // Forced from the command line. + force_mesa || // Running over a rempote desktop, and the RemoteFX is not enabled, therefore Windows will only provide SW OpenGL 1.1 context. // In that case, use Mesa. ::GetSystemMetrics(SM_REMOTESESSION) || // Try to load the default OpenGL driver and test its context version. ! opengl_version_check.load_opengl_dll() || ! opengl_version_check.is_version_greater_or_equal_to(2, 0); +#endif /* SLIC3R_GUI */ wchar_t path_to_exe[MAX_PATH + 1] = { 0 }; ::GetModuleFileNameW(nullptr, path_to_exe, MAX_PATH); @@ -228,6 +251,7 @@ int wmain(int argc, wchar_t **argv) _wsplitpath(path_to_exe, drive, dir, fname, ext); _wmakepath(path_to_exe, drive, dir, nullptr, nullptr); +#ifdef SLIC3R_GUI // https://wiki.qt.io/Cross_compiling_Mesa_for_Windows // http://download.qt.io/development_releases/prebuilt/llvmpipe/windows/ if (load_mesa) { @@ -242,6 +266,8 @@ int wmain(int argc, wchar_t **argv) } else printf("MESA OpenGL library was loaded sucessfully\n"); } +#endif /* SLIC3R_GUI */ + wchar_t path_to_slic3r[MAX_PATH + 1] = { 0 }; wcscpy(path_to_slic3r, path_to_exe); @@ -267,5 +293,5 @@ int wmain(int argc, wchar_t **argv) return -1; } // argc minus the trailing nullptr of the argv - return slic3r_main(argv_extended.size() - 1, argv_extended.data()); + return slic3r_main((int)argv_extended.size() - 1, argv_extended.data()); } diff --git a/version.inc b/version.inc index 18ac508b2a..6a3f00e00e 100644 --- a/version.inc +++ b/version.inc @@ -2,7 +2,7 @@ # (the version numbers are generated by the build script from the git current label) set(SLIC3R_FORK_NAME "Slic3r Prusa Edition") -set(SLIC3R_VERSION "1.42.0-beta") +set(SLIC3R_VERSION "1.42.0-beta2") set(SLIC3R_BUILD "${SLIC3R_VERSION}+UNKNOWN") set(SLIC3R_BUILD_ID "${SLIC3R_BUILD_ID}") set(SLIC3R_RC_VERSION "1,42,0,0")