diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 78da38b13..4eb1837c7 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -37,6 +37,12 @@ #include +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +#include +#include +#include +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + #ifdef HAS_GLSAFE void glAssertRecentCallImpl(const char* file_name, unsigned int line, const char* function_name) { @@ -614,6 +620,106 @@ void GLVolume::render_sinking_contours() m_sinking_contours.render(); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +void GLVolume::calc_convex_hull_3d() +{ + if (this->indexed_vertex_array.vertices_and_normals_interleaved.empty()) + return; + + TriangleMesh mesh; + for (size_t i = 0; i < this->indexed_vertex_array.vertices_and_normals_interleaved.size(); i += 6) { + const size_t v_id = 3 + i; + mesh.its.vertices.push_back({ this->indexed_vertex_array.vertices_and_normals_interleaved[v_id + 0], + this->indexed_vertex_array.vertices_and_normals_interleaved[v_id + 1], + this->indexed_vertex_array.vertices_and_normals_interleaved[v_id + 2] + }); + } + + const std::vector& vertices = mesh.its.vertices; + + // The qhull call: + orgQhull::Qhull qhull; + qhull.disableOutputStream(); // we want qhull to be quiet + std::vector src_vertices; + try + { +#if REALfloat + qhull.runQhull("", 3, (int)vertices.size(), (const realT*)(vertices.front().data()), "Qt"); +#else + src_vertices.reserve(vertices.size() * 3); + // We will now fill the vector with input points for computation: + for (const stl_vertex& v : vertices) + for (int i = 0; i < 3; ++i) + src_vertices.emplace_back(v(i)); + qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); +#endif + } + catch (...) + { + std::cout << "GLVolume::calc_convex_hull_3d() - Unable to create convex hull" << std::endl; + return ; + } + + // Let's collect results: + std::vector dst_vertices; + std::vector dst_facets; + // Map of QHull's vertex ID to our own vertex ID (pointing to dst_vertices). + std::vector map_dst_vertices; +#ifndef NDEBUG + Vec3f centroid = Vec3f::Zero(); + for (const auto& pt : vertices) + centroid += pt; + centroid /= float(vertices.size()); +#endif // NDEBUG + for (const orgQhull::QhullFacet& facet : qhull.facetList()) { + // Collect face vertices first, allocate unique vertices in dst_vertices based on QHull's vertex ID. + Vec3i indices; + int cnt = 0; + for (const orgQhull::QhullVertex vertex : facet.vertices()) { + const int id = vertex.id(); + assert(id >= 0); + if (id >= int(map_dst_vertices.size())) + map_dst_vertices.resize(next_highest_power_of_2(size_t(id + 1)), -1); + if (int i = map_dst_vertices[id]; i == -1) { + // Allocate a new vertex. + i = int(dst_vertices.size()); + map_dst_vertices[id] = i; + orgQhull::QhullPoint pt(vertex.point()); + dst_vertices.emplace_back(pt[0], pt[1], pt[2]); + indices[cnt] = i; + } + else + // Reuse existing vertex. + indices[cnt] = i; + + if (cnt++ == 3) + break; + } + assert(cnt == 3); + if (cnt == 3) { + // QHull sorts vertices of a face lexicographically by their IDs, not by face normals. + // Calculate face normal based on the order of vertices. + const Vec3f n = (dst_vertices[indices(1)] - dst_vertices[indices(0)]).cross(dst_vertices[indices(2)] - dst_vertices[indices(1)]); + auto* n2 = facet.getBaseT()->normal; + const auto d = n.x() * n2[0] + n.y() * n2[1] + n.z() * n2[2]; +#ifndef NDEBUG + const Vec3f n3 = (dst_vertices[indices(0)] - centroid); + const auto d3 = n.dot(n3); + assert((d < 0.f) == (d3 < 0.f)); +#endif // NDEBUG + // Get the face normal from QHull. + if (d < 0.f) + // Fix face orientation. + std::swap(indices[1], indices[2]); + dst_facets.emplace_back(indices); + } + } + + TriangleMesh out_mesh{ std::move(dst_vertices), std::move(dst_facets) }; + this->set_convex_hull(out_mesh); +} +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + std::vector GLVolumeCollection::load_object( const ModelObject *model_object, int obj_idx, @@ -771,7 +877,10 @@ int GLVolumeCollection::load_wipe_tower_preview( volumes.emplace_back(new GLVolume(color)); GLVolume& v = *volumes.back(); - v.indexed_vertex_array.load_mesh(mesh); + v.indexed_vertex_array.load_mesh(mesh); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + v.calc_convex_hull_3d(); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS v.indexed_vertex_array.finalize_geometry(opengl_initialized); v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0)); v.set_volume_rotation(Vec3d(0., 0., (M_PI / 180.) * rotation_angle)); @@ -964,12 +1073,16 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M #if ENABLE_FIX_SINKING_OBJECT_OUT_OF_BED_DETECTION bool contained = false; bool intersects = false; - bool is_sla = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA; - const BoundingBoxf3 bb = is_sla ? volume->transformed_convex_hull_bounding_box() : volume->transformed_non_sinking_bounding_box(); + bool is_aux_volume = volume->is_sla_support() || volume->is_sla_pad() || volume->is_wipe_tower; + const BoundingBoxf3 bb = is_aux_volume ? volume->transformed_convex_hull_bounding_box() : volume->transformed_non_sinking_bounding_box(); #if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS ModelInstanceEPrintVolumeState volume_state; - if (is_sla) - volume_state = printbed_collision_state(bed_poly, bed_height, bb); + if (is_aux_volume) { + if (volume->is_sla_support() || volume->is_wipe_tower) { + const Polygon volume_hull_2d = its_convex_hull_2d_above(volume->convex_hull()->its, volume->world_matrix().cast(), 0.0f); + volume_state = printbed_collision_state(bed_poly, bed_height, volume_hull_2d, bb.min.z(), bb.max.z()); + } + } else { const indexed_triangle_set& its = GUI::wxGetApp().plater()->model().objects[volume->object_idx()]->volumes[volume->volume_idx()]->mesh().its; const Polygon volume_hull_2d = its_convex_hull_2d_above(its, volume->world_matrix().cast(), 0.0f); @@ -1061,35 +1174,28 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con std::vector colors(colors_count); unsigned char rgb[3]; - for (unsigned int i = 0; i < colors_count; ++i) - { + for (unsigned int i = 0; i < colors_count; ++i) { const std::string& txt_color = config->opt_string("extruder_colour", i); if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb)) - { colors[i].set(txt_color, rgb); - } - else - { + else { const std::string& txt_color = config->opt_string("filament_colour", i); if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb)) colors[i].set(txt_color, rgb); } } - for (GLVolume* volume : volumes) - { - if ((volume == nullptr) || volume->is_modifier || volume->is_wipe_tower || (volume->volume_idx() < 0)) + for (GLVolume* volume : volumes) { + if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || (volume->volume_idx() < 0)) continue; int extruder_id = volume->extruder_id - 1; - if ((extruder_id < 0) || ((int)colors.size() <= extruder_id)) + if (extruder_id < 0 || (int)colors.size() <= extruder_id) extruder_id = 0; const Color& color = colors[extruder_id]; - if (!color.text.empty()) - { - for (int i = 0; i < 3; ++i) - { + if (!color.text.empty()) { + for (int i = 0; i < 3; ++i) { volume->color[i] = (float)color.rgb[i] * inv_255; } } diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 7df772c7f..8aaf48549 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -515,6 +515,12 @@ public: // Return an estimate of the memory held by GPU vertex buffers. size_t gpu_memory_used() const { return this->indexed_vertex_array.gpu_memory_used(); } size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } + +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + // calculates the 3D convex hull from indexed_vertex_array.vertices_and_normals_interleaved + // must be called before calling indexed_vertex_array.finalize_geometry(); + void calc_convex_hull_3d(); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS }; typedef std::vector GLVolumePtrs; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 305cdd9d3..84c5053ef 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1826,8 +1826,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re assert(volume_idx_wipe_tower_old == -1); volume_idx_wipe_tower_old = (int)volume_id; } - if (!m_reload_delayed) - { + if (!m_reload_delayed) { deleted_volumes.emplace_back(volume, volume_id); delete volume; } @@ -5016,8 +5015,7 @@ void GLCanvas3D::_render_background() const #if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS ModelInstanceEPrintVolumeState state; if (m_gcode_viewer.has_data()) { - const ConfigOptionPoints* opt = dynamic_cast(m_config->option("bed_shape")); - const Polygon bed_poly = offset(Polygon::new_scale(opt->values), static_cast(scale_(BedEpsilon))).front(); + const Polygon bed_poly = offset(Polygon::new_scale(wxGetApp().plater()->get_bed().get_shape()), static_cast(scale_(BedEpsilon))).front(); const float bed_height = m_config->opt_float("max_print_height"); state = printbed_collision_state(bed_poly, bed_height, m_gcode_viewer.get_paths_bounding_box()); } @@ -5813,7 +5811,7 @@ void GLCanvas3D::_load_print_toolpaths() total_layer_count = std::max(total_layer_count, print_object->total_layer_count()); } size_t skirt_height = print->has_infinite_skirt() ? total_layer_count : std::min(print->config().skirt_height.value, total_layer_count); - if ((skirt_height == 0) && print->has_brim()) + if (skirt_height == 0 && print->has_brim()) skirt_height = 1; // Get first skirt_height layers. @@ -5846,6 +5844,9 @@ void GLCanvas3D::_load_print_toolpaths() reserve_new_volume_finalize_old_volume(*volume, vol, m_initialized); } } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + volume->calc_convex_hull_3d(); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS volume->indexed_vertex_array.finalize_geometry(m_initialized); } @@ -6136,8 +6137,16 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), [](const GLVolume *volume) { return volume->empty(); }), m_volumes.volumes.end()); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { + GLVolume* v = m_volumes.volumes[i]; + v->calc_convex_hull_3d(); + v->indexed_vertex_array.finalize_geometry(m_initialized); + } +#else for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_initialized); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); } @@ -6145,7 +6154,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_tool_colors) { const Print *print = this->fff_print(); - if ((print == nullptr) || print->wipe_tower_data().tool_changes.empty()) + if (print == nullptr || print->wipe_tower_data().tool_changes.empty()) return; if (!print->is_step_done(psWipeTower)) @@ -6293,8 +6302,16 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), [](const GLVolume *volume) { return volume->empty(); }), m_volumes.volumes.end()); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { + GLVolume* v = m_volumes.volumes[i]; + v->calc_convex_hull_3d(); + v->indexed_vertex_array.finalize_geometry(m_initialized); + } +#else for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_initialized); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); } @@ -6357,12 +6374,13 @@ void GLCanvas3D::_load_sla_shells() void GLCanvas3D::_update_toolpath_volumes_outside_state() { #if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - const ConfigOptionPoints* opt = dynamic_cast(m_config->option("bed_shape")); - const Polygon bed_poly = offset(Polygon::new_scale(opt->values), static_cast(scale_(BedEpsilon))).front(); + const Polygon bed_poly = offset(Polygon::new_scale(wxGetApp().plater()->get_bed().get_shape()), static_cast(scale_(BedEpsilon))).front(); const float bed_height = m_config->opt_float("max_print_height"); for (GLVolume* volume : m_volumes.volumes) { if (volume->is_extrusion_path) { - const ModelInstanceEPrintVolumeState state = printbed_collision_state(bed_poly, bed_height, volume->bounding_box()); + const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box(); + const Polygon volume_hull_2d = its_convex_hull_2d_above(volume->convex_hull()->its, volume->world_matrix().cast(), 0.0f); + const ModelInstanceEPrintVolumeState state = printbed_collision_state(bed_poly, bed_height, volume_hull_2d, bb.min.z(), bb.max.z()); volume->is_outside = (state != ModelInstancePVS_Inside); } else @@ -6379,12 +6397,13 @@ void GLCanvas3D::_update_toolpath_volumes_outside_state() void GLCanvas3D::_update_sla_shells_outside_state() { #if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - const ConfigOptionPoints* opt = dynamic_cast(m_config->option("bed_shape")); - const Polygon bed_poly = offset(Polygon::new_scale(opt->values), static_cast(scale_(BedEpsilon))).front(); + const Polygon bed_poly = offset(Polygon::new_scale(wxGetApp().plater()->get_bed().get_shape()), static_cast(scale_(BedEpsilon))).front(); const float bed_height = m_config->opt_float("max_print_height"); for (GLVolume* volume : m_volumes.volumes) { if (volume->shader_outside_printer_detection_enabled) { - const ModelInstanceEPrintVolumeState state = printbed_collision_state(bed_poly, bed_height, volume->transformed_convex_hull_bounding_box()); + const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box(); + const Polygon volume_hull_2d = its_convex_hull_2d_above(volume->convex_hull()->its, volume->world_matrix().cast(), 0.0f); + const ModelInstanceEPrintVolumeState state = printbed_collision_state(bed_poly, bed_height, volume_hull_2d, bb.min.z(), bb.max.z()); volume->is_outside = (state != ModelInstancePVS_Inside); } else @@ -6407,11 +6426,12 @@ void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) else { if (wxGetApp().is_editor()) { #if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - const ConfigOptionPoints* opt = dynamic_cast(m_config->option("bed_shape")); - const Polygon bed_poly = offset(Polygon::new_scale(opt->values), static_cast(scale_(BedEpsilon))).front(); - const float bed_height = m_config->opt_float("max_print_height"); - const ModelInstanceEPrintVolumeState state = printbed_collision_state(bed_poly, bed_height, m_gcode_viewer.get_paths_bounding_box()); - show = state != ModelInstancePVS_Inside; + if (current_printer_technology() != ptSLA) { + const Polygon bed_poly = offset(Polygon::new_scale(wxGetApp().plater()->get_bed().get_shape()), static_cast(scale_(BedEpsilon))).front(); + const float bed_height = m_config->opt_float("max_print_height"); + const ModelInstanceEPrintVolumeState state = printbed_collision_state(bed_poly, bed_height, m_gcode_viewer.get_paths_bounding_box()); + show = state != ModelInstancePVS_Inside; + } #else BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); const BoundingBoxf3& paths_volume = m_gcode_viewer.get_paths_bounding_box(); @@ -6464,7 +6484,7 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) "Resolve the current problem to continue slicing."); error = ErrorType::PLATER_ERROR; break; -} + } auto& notification_manager = *wxGetApp().plater()->get_notification_manager(); switch (error) { @@ -6494,7 +6514,7 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) bool GLCanvas3D::_is_any_volume_outside() const { for (const GLVolume* volume : m_volumes.volumes) { - if ((volume != nullptr) && volume->is_outside) + if (volume != nullptr && volume->is_outside) return true; }