diff --git a/resources/data/hints.ini b/resources/data/hints.ini index e7b1bb681..a79a8228a 100644 --- a/resources/data/hints.ini +++ b/resources/data/hints.ini @@ -48,6 +48,7 @@ # enabled_tags = ... # disabled_tags = ... # supported tags are: simple; advanced; expert; FFF; MMU; SLA; Windows; Linux; OSX; +# and all filament types: PLA; PET; ABS; ASA; FLEX; HIPS; EDGE; NGEN; NYLON; PVA; PC; PP; PEI; PEEK; PEKK; POM; PSU; PVDF; SCAFF; # Tags are case sensitive. # FFF is affirmative for both one or more extruder printers. # Algorithm shows hint only if ALL enabled tags are affirmative. (so never do enabled_tags = FFF; SLA;) diff --git a/resources/icons/notification_info.svg b/resources/icons/notification_info.svg new file mode 100644 index 000000000..e2db40745 --- /dev/null +++ b/resources/icons/notification_info.svg @@ -0,0 +1,67 @@ + +image/svg+xml + + + + diff --git a/sandboxes/opencsg/Engine.cpp b/sandboxes/opencsg/Engine.cpp index d8f1d3464..e64a47132 100644 --- a/sandboxes/opencsg/Engine.cpp +++ b/sandboxes/opencsg/Engine.cpp @@ -65,7 +65,7 @@ void CSGDisplay::render_scene() glFlush(); } -void Scene::set_print(uqptr &&print) +void Scene::set_print(std::unique_ptr &&print) { m_print = std::move(print); @@ -85,7 +85,7 @@ void CSGDisplay::SceneCache::clear() primitives.clear(); } -shptr CSGDisplay::SceneCache::add_mesh(const TriangleMesh &mesh) +std::shared_ptr CSGDisplay::SceneCache::add_mesh(const TriangleMesh &mesh) { auto p = std::make_shared(); p->load_mesh(mesh); @@ -94,7 +94,7 @@ shptr CSGDisplay::SceneCache::add_mesh(const TriangleMesh &mesh) return p; } -shptr CSGDisplay::SceneCache::add_mesh(const TriangleMesh &mesh, +std::shared_ptr CSGDisplay::SceneCache::add_mesh(const TriangleMesh &mesh, OpenCSG::Operation o, unsigned c) { diff --git a/sandboxes/opencsg/Engine.hpp b/sandboxes/opencsg/Engine.hpp index fc76c1b31..114268ddc 100644 --- a/sandboxes/opencsg/Engine.hpp +++ b/sandboxes/opencsg/Engine.hpp @@ -17,11 +17,6 @@ class SLAPrint; namespace GL { -// Simple shorthands for smart pointers -template using shptr = std::shared_ptr; -template using uqptr = std::unique_ptr; -template using wkptr = std::weak_ptr; - template> using vector = std::vector; // remove empty weak pointers from a vector @@ -61,7 +56,7 @@ public: }; private: - vector> m_listeners; + vector> m_listeners; public: virtual ~MouseInput() = default; @@ -95,7 +90,7 @@ public: call(&Listener::on_moved_to, m_listeners, x, y); } - void add_listener(shptr listener) + void add_listener(std::shared_ptr listener) { m_listeners.emplace_back(listener); cleanup(m_listeners); @@ -322,7 +317,7 @@ public: // The scene is a wrapper around SLAPrint which holds the data to be visualized. class Scene { - uqptr m_print; + std::unique_ptr m_print; public: // Subscribers will be notified if the model is changed. This might be a @@ -340,19 +335,19 @@ public: Scene(); ~Scene(); - void set_print(uqptr &&print); + void set_print(std::unique_ptr &&print); const SLAPrint * get_print() const { return m_print.get(); } BoundingBoxf3 get_bounding_box() const; - void add_listener(shptr listener) + void add_listener(std::shared_ptr listener) { m_listeners.emplace_back(listener); cleanup(m_listeners); } private: - vector> m_listeners; + vector> m_listeners; }; // The basic Display. This is almost just an interface but will do all the @@ -366,20 +361,20 @@ protected: Vec2i m_size; bool m_initialized = false; - shptr m_camera; + std::shared_ptr m_camera; FpsCounter m_fps_counter; public: - explicit Display(shptr camera = nullptr) + explicit Display(std::shared_ptr camera = nullptr) : m_camera(camera ? camera : std::make_shared()) {} ~Display() override; - shptr get_camera() const { return m_camera; } - shptr get_camera() { return m_camera; } - void set_camera(shptr cam) { m_camera = cam; } + std::shared_ptr get_camera() const { return m_camera; } + std::shared_ptr get_camera() { return m_camera; } + void set_camera(std::shared_ptr cam) { m_camera = cam; } virtual void swap_buffers() = 0; virtual void set_active(long width, long height); @@ -410,14 +405,14 @@ protected: // Cache the renderable primitives. These will be fetched when the scene // is modified. struct SceneCache { - vector> primitives; + vector> primitives; vector primitives_free; vector primitives_csg; void clear(); - shptr add_mesh(const TriangleMesh &mesh); - shptr add_mesh(const TriangleMesh &mesh, + std::shared_ptr add_mesh(const TriangleMesh &mesh); + std::shared_ptr add_mesh(const TriangleMesh &mesh, OpenCSG::Operation op, unsigned covexity); } m_scene_cache; @@ -446,13 +441,13 @@ class Controller : public std::enable_shared_from_this, Vec2i m_mouse_pos, m_mouse_pos_rprev, m_mouse_pos_lprev; bool m_left_btn = false, m_right_btn = false; - shptr m_scene; - vector> m_displays; + std::shared_ptr m_scene; + vector> m_displays; // Call a method of Camera on all the cameras of the attached displays template void call_cameras(F &&f, Args&&... args) { - for (wkptr &l : m_displays) + for (std::weak_ptr &l : m_displays) if (auto disp = l.lock()) if (auto cam = disp->get_camera()) (cam.get()->*f)(std::forward(args)...); } @@ -460,7 +455,7 @@ class Controller : public std::enable_shared_from_this, public: // Set the scene that will be controlled. - void set_scene(shptr scene) + void set_scene(std::shared_ptr scene) { m_scene = scene; m_scene->add_listener(shared_from_this()); @@ -468,7 +463,7 @@ public: const Scene * get_scene() const { return m_scene.get(); } - void add_display(shptr disp) + void add_display(std::shared_ptr disp) { m_displays.emplace_back(disp); cleanup(m_displays); diff --git a/sandboxes/opencsg/ShaderCSGDisplay.hpp b/sandboxes/opencsg/ShaderCSGDisplay.hpp index bf0c3a424..0e2c763df 100644 --- a/sandboxes/opencsg/ShaderCSGDisplay.hpp +++ b/sandboxes/opencsg/ShaderCSGDisplay.hpp @@ -12,7 +12,7 @@ class CSGVolume: public Volume class ShaderCSGDisplay: public Display { protected: - vector> m_volumes; + vector> m_volumes; void add_mesh(const TriangleMesh &mesh); public: diff --git a/sandboxes/opencsg/main.cpp b/sandboxes/opencsg/main.cpp index f5fb12493..f0627b974 100644 --- a/sandboxes/opencsg/main.cpp +++ b/sandboxes/opencsg/main.cpp @@ -34,7 +34,7 @@ using namespace Slic3r::GL; class Renderer { protected: wxGLCanvas *m_canvas; - shptr m_context; + std::shared_ptr m_context; public: Renderer(wxGLCanvas *c): m_canvas{c} { @@ -86,16 +86,16 @@ public: class Canvas: public wxGLCanvas { // One display is active at a time, the OCSGRenderer by default. - shptr m_display; + std::shared_ptr m_display; public: template Canvas(Args &&...args): wxGLCanvas(std::forward(args)...) {} - shptr get_display() const { return m_display; } + std::shared_ptr get_display() const { return m_display; } - void set_display(shptr d) { m_display = d; } + void set_display(std::shared_ptr d) { m_display = d; } }; // Enumerate possible mouse events, we will record them. @@ -197,14 +197,14 @@ public: class MyFrame: public wxFrame { // Instantiate the 3D engine. - shptr m_scene; // Model - shptr m_canvas; // Views store - shptr m_ocsgdisplay; // View - shptr m_shadercsg_display; // Another view - shptr m_ctl; // Controller + std::shared_ptr m_scene; // Model + std::shared_ptr m_canvas; // Views store + std::shared_ptr m_ocsgdisplay; // View + std::shared_ptr m_shadercsg_display; // Another view + std::shared_ptr m_ctl; // Controller // Add a status bar with progress indication. - shptr m_stbar; + std::shared_ptr m_stbar; RecorderMouseInput m_mouse; @@ -237,7 +237,7 @@ class MyFrame: public wxFrame } }; - uqptr m_ui_job; + std::unique_ptr m_ui_job; // To keep track of the running average of measured fps values. double m_fps_avg = 0.; diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index 713499a1b..c5627f16b 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -149,10 +149,11 @@ namespace ImGui const wchar_t CustomSupportsMarker = 0x1D; const wchar_t CustomSeamMarker = 0x1E; const wchar_t MmuSegmentationMarker = 0x1F; + // Do not forget use following letters only in wstring const wchar_t DocumentationButton = 0x2600; const wchar_t DocumentationHoverButton = 0x2601; const wchar_t ClippyMarker = 0x2602; - + const wchar_t InfoMarker = 0x2603; // void MyFunction(const char* name, const MyMatrix44& v); } diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index f0807df51..bcc5c8b4a 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -617,19 +617,35 @@ void AMFParserContext::endElement(const char * /* name */) case NODE_TYPE_VOLUME: { assert(m_object && m_volume); - // Verify validity of face indices. - for (Vec3i face : m_volume_facets) - for (unsigned int tri_id : face) - if (tri_id < 0 || tri_id >= m_object_vertices.size()) { - this->stop("Malformed triangle mesh"); - return; - } + if (m_volume_facets.empty()) { + this->stop("An empty triangle mesh found"); + return; + } { - TriangleMesh triangle_mesh { std::move(m_object_vertices), std::move(m_volume_facets) }; - if (triangle_mesh.volume() < 0) - triangle_mesh.flip_triangles(); - m_volume->set_mesh(std::move(triangle_mesh)); + // Verify validity of face indices, find the vertex span. + int min_id = m_volume_facets.front()[0]; + int max_id = min_id; + for (const Vec3i& face : m_volume_facets) { + for (const int tri_id : face) { + if (tri_id < 0 || tri_id >= int(m_object_vertices.size())) { + this->stop("Malformed triangle mesh"); + return; + } + min_id = std::min(min_id, tri_id); + max_id = std::max(max_id, tri_id); + } + } + + // rebase indices to the current vertices list + for (Vec3i &face : m_volume_facets) + face -= Vec3i(min_id, min_id, min_id); + + indexed_triangle_set its { std::move(m_volume_facets), { m_object_vertices.begin() + min_id, m_object_vertices.begin() + max_id + 1 } }; + its_compactify_vertices(its); + if (its_volume(its) < 0) + its_flip_triangles(its); + m_volume->set_mesh(std::move(its)); } // stores the volume matrix taken from the metadata, if present @@ -646,7 +662,6 @@ void AMFParserContext::endElement(const char * /* name */) m_volume->calculate_convex_hull(); m_volume_facets.clear(); - m_object_vertices.clear(); m_volume = nullptr; break; } diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index 4ddc584ba..c4b8f030f 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -423,7 +423,7 @@ void fill_slicerconf(ConfMap &m, const SLAPrint &print) } // namespace -uqptr SL1Archive::create_raster() const +std::unique_ptr SL1Archive::create_raster() const { sla::RasterBase::Resolution res; sla::RasterBase::PixelDim pxdim; diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp index 7f4356b12..46a82e1b8 100644 --- a/src/libslic3r/Format/SL1.hpp +++ b/src/libslic3r/Format/SL1.hpp @@ -12,7 +12,7 @@ class SL1Archive: public SLAArchive { SLAPrinterConfig m_cfg; protected: - uqptr create_raster() const override; + std::unique_ptr create_raster() const override; sla::RasterEncoder get_encoder() const override; public: diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 2397a17ff..9c90535c4 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1456,7 +1456,7 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename) begin = skip_whitespaces(begin, end); end = remove_eols(begin, end); - if (begin != end) + if (begin != end) { if (*begin == ';') { // Comment. begin = skip_whitespaces(++ begin, end); @@ -1485,6 +1485,7 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename) // Some non-empty G-code line detected, stop parsing config comments. reader.quit_parsing(); } + } }); if (m_result.extruders_count == 0) @@ -2860,6 +2861,8 @@ void GCodeProcessor::process_M109(const GCodeReader::GCodeLine& line) else m_extruder_temps[m_extruder_id] = new_temp; } + else if (line.has_value('S', new_temp)) + m_extruder_temps[m_extruder_id] = new_temp; } void GCodeProcessor::process_M132(const GCodeReader::GCodeLine& line) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 321443204..420ab473f 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -20,6 +20,12 @@ #include #include +#if defined(_MSC_VER) && defined(__clang__) +#define BOOST_NO_CXX17_HDR_STRING_VIEW +#endif + +#include + #ifdef SLIC3R_DEBUG #include "SVG.hpp" #endif @@ -1543,4 +1549,205 @@ double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) return (axis.z() < 0) ? -angle : angle; } -} } +namespace rotcalip { + +using int256_t = boost::multiprecision::int256_t; +using int128_t = boost::multiprecision::int128_t; + +template +inline Scalar magnsq(const Point &p) +{ + return Scalar(p.x()) * p.x() + Scalar(p.y()) * p.y(); +} + +template +inline Scalar dot(const Point &a, const Point &b) +{ + return Scalar(a.x()) * b.x() + Scalar(a.y()) * b.y(); +} + +template +inline Scalar dotperp(const Point &a, const Point &b) +{ + return Scalar(a.x()) * b.y() - Scalar(a.y()) * b.x(); +} + +using boost::multiprecision::abs; + +// Compares the angle enclosed by vectors dir and dirA (alpha) with the angle +// enclosed by -dir and dirB (beta). Returns -1 if alpha is less than beta, 0 +// if they are equal and 1 if alpha is greater than beta. Note that dir is +// reversed for beta, because it represents the opposite side of a caliper. +int cmp_angles(const Point &dir, const Point &dirA, const Point &dirB) { + int128_t dotA = dot(dir, dirA); + int128_t dotB = dot(-dir, dirB); + int256_t dcosa = int256_t(magnsq(dirB)) * int256_t(abs(dotA)) * dotA; + int256_t dcosb = int256_t(magnsq(dirA)) * int256_t(abs(dotB)) * dotB; + int256_t diff = dcosa - dcosb; + + return diff > 0? -1 : (diff < 0 ? 1 : 0); +} + +// A helper class to navigate on a polygon. Given a vertex index, one can +// get the edge belonging to that vertex, the coordinates of the vertex, the +// next and previous edges. Stuff that is needed in the rotating calipers algo. +class Idx +{ + size_t m_idx; + const Polygon *m_poly; +public: + explicit Idx(const Polygon &p): m_idx{0}, m_poly{&p} {} + explicit Idx(size_t idx, const Polygon &p): m_idx{idx}, m_poly{&p} {} + + size_t idx() const { return m_idx; } + void set_idx(size_t i) { m_idx = i; } + size_t next() const { return (m_idx + 1) % m_poly->size(); } + size_t inc() { return m_idx = (m_idx + 1) % m_poly->size(); } + Point prev_dir() const { + return pt() - (*m_poly)[(m_idx + m_poly->size() - 1) % m_poly->size()]; + } + + const Point &pt() const { return (*m_poly)[m_idx]; } + const Point dir() const { return (*m_poly)[next()] - pt(); } + const Point next_dir() const + { + return (*m_poly)[(m_idx + 2) % m_poly->size()] - (*m_poly)[next()]; + } + const Polygon &poly() const { return *m_poly; } +}; + +enum class AntipodalVisitMode { Full, EdgesOnly }; + +// Visit all antipodal pairs starting from the initial ia, ib pair which +// has to be a valid antipodal pair (not checked). fn is called for every +// antipodal pair encountered including the initial one. +// The callback Fn has a signiture of bool(size_t i, size_t j, const Point &dir) +// where i,j are the vertex indices of the antipodal pair and dir is the +// direction of the calipers touching the i vertex. +template +void visit_antipodals (Idx& ia, Idx &ib, Fn &&fn) +{ + // Set current caliper direction to be the lower edge angle from X axis + int cmp = cmp_angles(ia.prev_dir(), ia.dir(), ib.dir()); + Idx *current = cmp <= 0 ? &ia : &ib, *other = cmp <= 0 ? &ib : &ia; + Idx *initial = current; + bool visitor_continue = true; + + size_t start = initial->idx(); + bool finished = false; + + while (visitor_continue && !finished) { + Point current_dir_a = current == &ia ? current->dir() : -current->dir(); + visitor_continue = fn(ia.idx(), ib.idx(), current_dir_a); + + // Parallel edges encountered. An additional pair of antipodals + // can be yielded. + if constexpr (mode == AntipodalVisitMode::Full) + if (cmp == 0 && visitor_continue) { + visitor_continue = fn(current == &ia ? ia.idx() : ia.next(), + current == &ib ? ib.idx() : ib.next(), + current_dir_a); + } + + cmp = cmp_angles(current->dir(), current->next_dir(), other->dir()); + + current->inc(); + if (cmp > 0) { + std::swap(current, other); + } + + if (initial->idx() == start) finished = true; + } +} + +} // namespace rotcalip + +bool intersects(const Polygon &A, const Polygon &B) +{ + using namespace rotcalip; + + // Establish starting antipodals as extremes in XY plane. Use the + // easily obtainable bounding boxes to check if A and B is disjoint + // and return false if the are. + struct BB + { + size_t xmin = 0, xmax = 0, ymin = 0, ymax = 0; + const Polygon &P; + static bool cmpy(const Point &l, const Point &u) + { + return l.y() < u.y() || (l.y() == u.y() && l.x() < u.x()); + } + + BB(const Polygon &poly): P{poly} + { + for (size_t i = 0; i < P.size(); ++i) { + if (P[i] < P[xmin]) xmin = i; + if (P[xmax] < P[i]) xmax = i; + if (cmpy(P[i], P[ymin])) ymin = i; + if (cmpy(P[ymax], P[i])) ymax = i; + } + } + }; + + BB bA{A}, bB{B}; + BoundingBox bbA{{A[bA.xmin].x(), A[bA.ymin].y()}, {A[bA.xmax].x(), A[bA.ymax].y()}}; + BoundingBox bbB{{B[bB.xmin].x(), B[bB.ymin].y()}, {B[bB.xmax].x(), B[bB.ymax].y()}}; + +// if (!bbA.overlap(bbB)) +// return false; + + // Establish starting antipodals as extreme vertex pairs in X or Y direction + // which reside on different polygons. If no such pair is found, the two + // polygons are certainly not disjoint. + Idx imin{bA.xmin, A}, imax{bB.xmax, B}; + if (B[bB.xmin] < imin.pt()) imin = Idx{bB.xmin, B}; + if (imax.pt() < A[bA.xmax]) imax = Idx{bA.xmax, A}; + if (&imin.poly() == &imax.poly()) { + imin = Idx{bA.ymin, A}; + imax = Idx{bB.ymax, B}; + if (B[bB.ymin] < imin.pt()) imin = Idx{bB.ymin, B}; + if (imax.pt() < A[bA.ymax]) imax = Idx{bA.ymax, A}; + } + + if (&imin.poly() == &imax.poly()) + return true; + + bool found_divisor = false; + visit_antipodals( + imin, imax, + [&imin, &imax, &found_divisor](size_t ia, size_t ib, const Point &dir) { + // std::cout << "A" << ia << " B" << ib << " dir " << + // dir.x() << " " << dir.y() << std::endl; + const Polygon &A = imin.poly(), &B = imax.poly(); + + Point ref_a = A[(ia + 2) % A.size()], ref_b = B[(ib + 2) % B.size()]; + + bool is_left_a = dotperp( dir, ref_a - A[ia]) > 0; + bool is_left_b = dotperp(-dir, ref_b - B[ib]) > 0; + + // If both reference points are on the left (or right) of their + // respective support lines and the opposite support line is to + // the right (or left), the divisor line is found. We only test + // the reference point, as by definition, if that is on one side, + // all the other points must be on the same side of a support + // line. If the support lines are collinear, the polygons must be + // on the same side of their respective support lines. + + auto d = dotperp(dir, B[ib] - A[ia]); + if (d == 0) { + // The caliper lines are collinear, not just parallel + found_divisor = (is_left_a && is_left_b) || (!is_left_a && !is_left_b); + } else if (d > 0) { // B is to the left of (A, A+1) + found_divisor = !is_left_a && !is_left_b; + } else { // B is to the right of (A, A+1) + found_divisor = is_left_a && is_left_b; + } + + return !found_divisor; + }); + + // Intersects if the divisor was not found + return !found_divisor; +} + +}} // namespace Slic3r::Geometry diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index c6af515c8..505ff3d96 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -532,6 +532,10 @@ inline bool is_rotation_ninety_degrees(const Vec3d &rotation) return is_rotation_ninety_degrees(rotation.x()) && is_rotation_ninety_degrees(rotation.y()) && is_rotation_ninety_degrees(rotation.z()); } +// Returns true if the intersection of the two convex polygons A and B +// is not an empty set. +bool intersects(const Polygon &A, const Polygon &B); + } } // namespace Slicer::Geometry #endif diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index da26f905c..0f70cf3cc 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1300,7 +1300,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ num_extruders, painting_extruders, *print_object_regions, - [&print_object, it_print_object, it_print_object_end, &update_apply_status](const PrintRegionConfig &old_config, const PrintRegionConfig &new_config, const t_config_option_keys &diff_keys) { + [it_print_object, it_print_object_end, &update_apply_status](const PrintRegionConfig &old_config, const PrintRegionConfig &new_config, const t_config_option_keys &diff_keys) { for (auto it = it_print_object; it != it_print_object_end; ++it) if ((*it)->m_shared_regions != nullptr) update_apply_status((*it)->invalidate_state_by_config_options(old_config, new_config, diff_keys)); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 74ef27bd7..f49eddf3e 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -397,22 +397,6 @@ void PrintObject::generate_support_material() if (layer->empty()) throw Slic3r::SlicingError("Levitating objects cannot be printed without supports."); #endif - - // Do we have custom support data that would not be used? - // Notify the user in that case. - if (! this->has_support()) { - for (const ModelVolume* mv : this->model_object()->volumes) { - bool has_enforcers = mv->is_support_enforcer() || - (mv->is_model_part() && mv->supported_facets.has_facets(*mv, EnforcerBlockerType::ENFORCER)); - if (has_enforcers) { - this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, - L("An object has custom support enforcers which will not be used " - "because supports are off. Consider turning them on.") + "\n" + - (L("Object name")) + ": " + this->model_object()->name); - break; - } - } - } } this->set_done(posSupportMaterial); } diff --git a/src/libslic3r/SLA/RasterBase.hpp b/src/libslic3r/SLA/RasterBase.hpp index 1cd688ccb..1eba360e3 100644 --- a/src/libslic3r/SLA/RasterBase.hpp +++ b/src/libslic3r/SLA/RasterBase.hpp @@ -13,10 +13,6 @@ namespace Slic3r { -template using uqptr = std::unique_ptr; -template using shptr = std::shared_ptr; -template using wkptr = std::weak_ptr; - namespace sla { // Raw byte buffer paired with its size. Suitable for compressed image data. @@ -112,7 +108,7 @@ struct PPMRasterEncoder { std::ostream& operator<<(std::ostream &stream, const EncodedRaster &bytes); // If gamma is zero, thresholding will be performed which disables AA. -uqptr create_raster_grayscale_aa( +std::unique_ptr create_raster_grayscale_aa( const RasterBase::Resolution &res, const RasterBase::PixelDim & pxdim, double gamma = 1.0, diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index deaabbe19..833146985 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -391,7 +391,7 @@ class SLAArchive { protected: std::vector m_layers; - virtual uqptr create_raster() const = 0; + virtual std::unique_ptr create_raster() const = 0; virtual sla::RasterEncoder get_encoder() const = 0; public: diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 799e135ce..2099cbf73 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -2055,21 +2055,21 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts( // Trim the already created base layers above the current layer intersecting with the new bottom contacts layer. //FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage? touching = offset(touching, float(SCALED_EPSILON)); - for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++layer_id_above) { + for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) { const Layer &layer_above = *object.layers()[layer_id_above]; if (layer_above.print_z > layer_new.print_z - EPSILON) break; - if (! layer_support_areas[layer_id_above].empty()) { + if (Polygons &above = layer_support_areas[layer_id_above]; ! above.empty()) { #ifdef SLIC3R_DEBUG SVG::export_expolygons(debug_out_path("support-support-areas-raw-before-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z), - { { { union_ex(touching) }, { "touching", "blue", 0.5f } }, - { { union_safety_offset_ex(layer_support_areas[layer_id_above]) }, { "above", "red", "black", "", scaled(0.1f), 0.5f } } }); + { { { union_ex(touching) }, { "touching", "blue", 0.5f } }, + { { union_safety_offset_ex(above) }, { "above", "red", "black", "", scaled(0.1f), 0.5f } } }); #endif /* SLIC3R_DEBUG */ - layer_support_areas[layer_id_above] = diff(layer_support_areas[layer_id_above], touching); + above = diff(above, touching); #ifdef SLIC3R_DEBUG Slic3r::SVG::export_expolygons( debug_out_path("support-support-areas-raw-after-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z), - union_ex(layer_support_areas[layer_id_above])); + union_ex(above)); #endif /* SLIC3R_DEBUG */ } } @@ -2622,10 +2622,9 @@ void PrintObjectSupportMaterial::generate_base_layers( // New polygons for layer_intermediate. Polygons polygons_new; - // Use the precomputed layer_support_areas. - idx_object_layer_above = std::max(0, idx_lower_or_equal(object.layers().begin(), object.layers().end(), idx_object_layer_above, - [&layer_intermediate](const Layer *layer){ return layer->print_z <= layer_intermediate.print_z + EPSILON; })); - polygons_new = layer_support_areas[idx_object_layer_above]; + // Use the precomputed layer_support_areas. "idx_object_layer_above": above means above since the last iteration, not above after this call. + idx_object_layer_above = idx_lower_or_equal(object.layers().begin(), object.layers().end(), idx_object_layer_above, + [&layer_intermediate](const Layer* layer) { return layer->print_z <= layer_intermediate.print_z + EPSILON; }); // Polygons to trim polygons_new. Polygons polygons_trimming; @@ -2640,7 +2639,7 @@ void PrintObjectSupportMaterial::generate_base_layers( idx_top_contact_above = idx_lower_or_equal(top_contacts, idx_top_contact_above, [&layer_intermediate](const MyLayer *layer){ return layer->bottom_z <= layer_intermediate.print_z - EPSILON; }); // Collect all the top_contact layer intersecting with this layer. - for ( int idx_top_contact_overlapping = idx_top_contact_above; idx_top_contact_overlapping >= 0; -- idx_top_contact_overlapping) { + for (int idx_top_contact_overlapping = idx_top_contact_above; idx_top_contact_overlapping >= 0; -- idx_top_contact_overlapping) { MyLayer &layer_top_overlapping = *top_contacts[idx_top_contact_overlapping]; if (layer_top_overlapping.print_z < layer_intermediate.bottom_z + EPSILON) break; @@ -2651,6 +2650,22 @@ void PrintObjectSupportMaterial::generate_base_layers( polygons_append(polygons_trimming, layer_top_overlapping.polygons); } + if (idx_object_layer_above < 0) { + // layer_support_areas are synchronized with object layers and they contain projections of the contact layers above them. + // This intermediate layer is not above any object layer, thus there is no information in layer_support_areas about + // towers supporting contact layers intersecting the first object layer. Project these contact layers now. + polygons_new = layer_support_areas.front(); + double first_layer_z = object.layers().front()->print_z; + for (int i = idx_top_contact_above + 1; i < int(top_contacts.size()); ++ i) { + MyLayer &contacts = *top_contacts[i]; + if (contacts.print_z > first_layer_z + EPSILON) + break; + assert(contacts.bottom_z > layer_intermediate.print_z - EPSILON); + polygons_append(polygons_new, contacts.polygons); + } + } else + polygons_new = layer_support_areas[idx_object_layer_above]; + // Trimming the base layer with any overlapping bottom layer. // Following cases are recognized: // 1) bottom.bottom_z >= base.top_z -> No overlap, no trimming needed. diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 39815bd64..7285fc8fa 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -325,20 +325,24 @@ void TriangleMesh::mirror(const Axis axis) void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed) { its_transform(its, t); - if (fix_left_handed && t.matrix().block(0, 0, 3, 3).determinant() < 0.) + double det = t.matrix().block(0, 0, 3, 3).determinant(); + if (fix_left_handed && det < 0.) { its_flip_triangles(its); - else - m_stats.volume = - m_stats.volume; + det = -det; + } + m_stats.volume *= det; update_bounding_box(this->its, this->m_stats); } void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed) { its_transform(its, m); - if (fix_left_handed && m.determinant() < 0.) + double det = m.block(0, 0, 3, 3).determinant(); + if (fix_left_handed && det < 0.) { its_flip_triangles(its); - else - m_stats.volume = - m_stats.volume; + det = -det; + } + m_stats.volume *= det; update_bounding_box(this->its, this->m_stats); } @@ -461,7 +465,7 @@ TriangleMesh TriangleMesh::convex_hull_3d() const std::vector map_dst_vertices; #ifndef NDEBUG Vec3f centroid = Vec3f::Zero(); - for (auto pt : this->its.vertices) + for (const stl_vertex& pt : this->its.vertices) centroid += pt; centroid /= float(this->its.vertices.size()); #endif // NDEBUG @@ -1257,7 +1261,7 @@ bool its_write_stl_ascii(const char *file, const char *label, const std::vector< fprintf(fp, "solid %s\n", label); - for (const stl_triangle_vertex_indices face : indices) { + for (const stl_triangle_vertex_indices& face : indices) { Vec3f vertex[3] = { vertices[face(0)], vertices[face(1)], vertices[face(2)] }; Vec3f normal = (vertex[1] - vertex[0]).cross(vertex[2] - vertex[1]).normalized(); fprintf(fp, " facet normal % .8E % .8E % .8E\n", normal(0), normal(1), normal(2)); @@ -1297,7 +1301,7 @@ bool its_write_stl_binary(const char *file, const char *label, const std::vector stl_facet f; f.extra[0] = 0; f.extra[1] = 0; - for (const stl_triangle_vertex_indices face : indices) { + for (const stl_triangle_vertex_indices& face : indices) { f.vertex[0] = vertices[face(0)]; f.vertex[1] = vertices[face(1)]; f.vertex[2] = vertices[face(2)]; diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index ee6e3d5ab..4d50e1490 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -30,6 +30,8 @@ #include "libslic3r/Platform.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Config.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/Model.hpp" #include "GUI.hpp" #include "GUI_App.hpp" #include "GUI_Utils.hpp" @@ -40,7 +42,6 @@ #include "slic3r/Utils/PresetUpdater.hpp" #include "format.hpp" #include "MsgDialog.hpp" -#include "libslic3r/libslic3r.h" #include "UnsavedChangesDialog.hpp" #if defined(__linux__) && defined(__WXGTK3__) @@ -2477,6 +2478,46 @@ static std::string get_first_added_preset(const std::mapmodels) { + if (const auto model_it = config->second.find(model.id); + model_it != config->second.end() && model_it->second.size() > 0) { + if (pt == ptAny) + pt = model.technology; + // if preferred printer model has SLA printer technology it's important to check the model for multypart state + if (pt == ptSLA && suppress_sla_printer) + continue; + else + return pt; + } + } + } + return pt; + }; + // Prusa printers are considered first, then 3rd party. + if (preferred_pt = get_preferred_printer_technology("PrusaResearch", bundles.prusa_bundle()); + preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer)) { + for (const auto& bundle : bundles) { + if (bundle.second.is_prusa_bundle) { continue; } + if (PrinterTechnology pt = get_preferred_printer_technology(bundle.first, bundle.second); pt == ptAny) + continue; + else if (preferred_pt == ptAny) + preferred_pt = pt; + if(!(preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer))) + break; + } + } + + if (preferred_pt == ptSLA && !wxGetApp().may_switch_to_SLA_preset(caption)) + return false; + bool check_unsaved_preset_changes = page_welcome->reset_user_profile(); if (check_unsaved_preset_changes) header = _L("All user presets will be deleted."); @@ -2484,8 +2525,6 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese if (!check_unsaved_preset_changes) act_btns |= UnsavedChangesDialog::ActionButtons::SAVE; - const auto enabled_vendors = appconfig_new.vendors(); - // Install bundles from resources if needed: std::vector install_bundles; for (const auto &pair : bundles) { @@ -2564,13 +2603,14 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese std::string preferred_model; std::string preferred_variant; const auto enabled_vendors_old = app_config->vendors(); - auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old](const std::string& bundle_name, const Bundle& bundle, std::string& variant) { + auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old, preferred_pt](const std::string& bundle_name, const Bundle& bundle, std::string& variant) { const auto config = enabled_vendors.find(bundle_name); if (config == enabled_vendors.end()) return std::string(); for (const auto& model : bundle.vendor_profile->models) { if (const auto model_it = config->second.find(model.id); - model_it != config->second.end() && model_it->second.size() > 0) { + model_it != config->second.end() && model_it->second.size() > 0 && + preferred_pt == model.technology) { variant = *model_it->second.begin(); const auto config_old = enabled_vendors_old.find(bundle_name); if (config_old == enabled_vendors_old.end()) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 9c08159fc..41f4c4b89 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2432,6 +2432,20 @@ void GUI_App::open_web_page_localized(const std::string &http_address) open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe()); } +// If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s). +// Because of we can't to print the multi-part objects with SLA technology. +bool GUI_App::may_switch_to_SLA_preset(const wxString& caption) +{ + if (model_has_multi_part_objects(model())) { + show_info(nullptr, + _L("It's impossible to print multi-part object(s) with SLA technology.") + "\n\n" + + _L("Please check your object list before preset changing."), + caption); + return false; + } + return true; +} + bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page) { wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); @@ -2447,13 +2461,9 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage if (res) { load_current_presets(); - if (preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA - && Slic3r::model_has_multi_part_objects(wxGetApp().model())) { - GUI::show_info(nullptr, - _L("It's impossible to print multi-part object(s) with SLA technology.") + "\n\n" + - _L("Please check and fix your object list."), - _L("Attention!")); - } + // #ysFIXME - delete after testing: This part of code looks redundant. All checks are inside ConfigWizard::priv::apply_config() + if (preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) + may_switch_to_SLA_preset(_L("Configuration is editing from ConfigWizard")); } return res; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 60a143144..a581cf8b3 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -309,6 +309,7 @@ public: PrintHostJobQueue& printhost_job_queue() { return *m_printhost_job_queue.get(); } void open_web_page_localized(const std::string &http_address); + bool may_switch_to_SLA_preset(const wxString& caption); bool run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page = ConfigWizard::SP_WELCOME); void show_desktop_integration_dialog(); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index b94110427..b83f5e0a6 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -377,54 +377,62 @@ void ObjectList::get_selection_indexes(std::vector& obj_idxs, std::vectorget_mesh_errors_count(vol_idx); + return obj_idx >= 0 ? (*m_objects)[obj_idx]->get_mesh_errors_count(vol_idx) : 0; } static std::string get_warning_icon_name(const TriangleMeshStats& stats) { - return stats.repaired() ? (stats.manifold() ? "exclamation_manifold" : "exclamation") : ""; + return stats.manifold() ? (stats.repaired() ? "exclamation_manifold" : "") : "exclamation"; } -std::pair ObjectList::get_mesh_errors(const int obj_idx, const int vol_idx /*= -1*/, bool from_plater /*= false*/) const +std::pair ObjectList::get_mesh_errors(const int obj_idx, const int vol_idx /*= -1*/, wxString* sidebar_info /*= nullptr*/) const { - const int errors = get_mesh_errors_count(obj_idx, vol_idx); - - if (errors == 0) - return { "", "" }; // hide tooltip - - // Create tooltip string, if there are errors - wxString tooltip = format_wxstr(_L_PLURAL("Auto-repaired %1$d error", "Auto-repaired %1$d errors", errors), errors) + ":\n"; - const TriangleMeshStats& stats = vol_idx == -1 ? (*m_objects)[obj_idx]->get_object_stl_stats() : (*m_objects)[obj_idx]->volumes[vol_idx]->mesh().stats(); - if (stats.degenerate_facets > 0) - tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d degenerate facet", "%1$d degenerate facets", stats.degenerate_facets), stats.degenerate_facets) + "\n"; - if (stats.edges_fixed > 0) - tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d edge fixed", "%1$d edges fixed", stats.edges_fixed), stats.edges_fixed) + "\n"; - if (stats.facets_removed > 0) - tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d facet removed", "%1$d facets removed", stats.facets_removed), stats.facets_removed) + "\n"; - if (stats.facets_reversed > 0) - tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d facet reversed", "%1$d facets reversed", stats.facets_reversed), stats.facets_reversed) + "\n"; - if (stats.backwards_edges > 0) - tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d backwards edge", "%1$d backwards edges", stats.backwards_edges), stats.backwards_edges) + "\n"; + if (!stats.repaired() && stats.manifold()) { + if (sidebar_info) + *sidebar_info = _L("No errors detected"); + return { {}, {} }; // hide tooltip + } + wxString tooltip, auto_repaired_info, remaining_info; + + // Create tooltip string, if there are errors + if (stats.repaired()) { + const int errors = get_mesh_errors_count(obj_idx, vol_idx); + auto_repaired_info = format_wxstr(_L_PLURAL("Auto-repaired %1$d error", "Auto-repaired %1$d errors", errors), errors); + tooltip += auto_repaired_info +":\n"; + + if (stats.degenerate_facets > 0) + tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d degenerate facet", "%1$d degenerate facets", stats.degenerate_facets), stats.degenerate_facets) + "\n"; + if (stats.edges_fixed > 0) + tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d edge fixed", "%1$d edges fixed", stats.edges_fixed), stats.edges_fixed) + "\n"; + if (stats.facets_removed > 0) + tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d facet removed", "%1$d facets removed", stats.facets_removed), stats.facets_removed) + "\n"; + if (stats.facets_reversed > 0) + tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d facet reversed", "%1$d facets reversed", stats.facets_reversed), stats.facets_reversed) + "\n"; + if (stats.backwards_edges > 0) + tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d backwards edge", "%1$d backwards edges", stats.backwards_edges), stats.backwards_edges) + "\n"; + } if (!stats.manifold()) { + remaining_info = format_wxstr(_L_PLURAL("Remaining %1$d open edge", "Remaining %1$d open edges", stats.open_edges), stats.open_edges); + tooltip += _L("Remaning errors") + ":\n"; tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d open edge", "%1$d open edges", stats.open_edges), stats.open_edges) + "\n"; } - if (is_windows10() && !from_plater) + if (sidebar_info) + *sidebar_info = stats.manifold() ? auto_repaired_info : (remaining_info + (stats.repaired() ? ("\n" + auto_repaired_info) : "")); + + if (is_windows10() && !sidebar_info) tooltip += "\n" + _L("Right button click the icon to fix STL through Netfabb"); return { tooltip, get_warning_icon_name(stats) }; } -std::pair ObjectList::get_mesh_errors(bool from_plater /*= false*/) +std::pair ObjectList::get_mesh_errors(wxString* sidebar_info /*= nullptr*/) { if (!GetSelection()) return { "", "" }; @@ -432,7 +440,7 @@ std::pair ObjectList::get_mesh_errors(bool from_plater /* int obj_idx, vol_idx; get_selected_item_indexes(obj_idx, vol_idx); - return get_mesh_errors(obj_idx, vol_idx, from_plater); + return get_mesh_errors(obj_idx, vol_idx, sidebar_info); } void ObjectList::set_tooltip_for_item(const wxPoint& pt) @@ -4043,17 +4051,21 @@ void ObjectList::fix_through_netfabb() // clear selections from the non-broken models if any exists // and than fill names of models to repairing if (vol_idxs.empty()) { +#if !FIX_THROUGH_NETFABB_ALWAYS for (int i = int(obj_idxs.size())-1; i >= 0; --i) if (object(obj_idxs[i])->get_mesh_errors_count() == 0) obj_idxs.erase(obj_idxs.begin()+i); +#endif // FIX_THROUGH_NETFABB_ALWAYS for (int obj_idx : obj_idxs) model_names.push_back(object(obj_idx)->name); } else { ModelObject* obj = object(obj_idxs.front()); +#if !FIX_THROUGH_NETFABB_ALWAYS for (int i = int(vol_idxs.size()) - 1; i >= 0; --i) if (obj->get_mesh_errors_count(vol_idxs[i]) == 0) vol_idxs.erase(vol_idxs.begin() + i); +#endif // FIX_THROUGH_NETFABB_ALWAYS for (int vol_idx : vol_idxs) model_names.push_back(obj->volumes[vol_idx]->name); } @@ -4106,8 +4118,10 @@ void ObjectList::fix_through_netfabb() if (vol_idxs.empty()) { int vol_idx{ -1 }; for (int obj_idx : obj_idxs) { +#if !FIX_THROUGH_NETFABB_ALWAYS if (object(obj_idx)->get_mesh_errors_count(vol_idx) == 0) continue; +#endif // FIX_THROUGH_NETFABB_ALWAYS if (!fix_and_update_progress(obj_idx, vol_idx, model_idx, progress_dlg, succes_models, failed_models)) break; model_idx++; diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 491bd2684..54e3f5d45 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -36,6 +36,9 @@ typedef double coordf_t; typedef std::pair t_layer_height_range; typedef std::map t_layer_config_ranges; +// Manifold mesh may contain self-intersections, so we want to always allow fixing the mesh. +#define FIX_THROUGH_NETFABB_ALWAYS 1 + namespace GUI { wxDECLARE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent); @@ -214,8 +217,8 @@ public: // Return value is a pair , used for the tooltip and related warning icon // Function without parameters is for a call from Manipulation panel, // when we don't know parameters of selected item - std::pair get_mesh_errors(const int obj_idx, const int vol_idx = -1, bool from_plater = false) const; - std::pair get_mesh_errors(bool from_plater = false); + std::pair get_mesh_errors(const int obj_idx, const int vol_idx = -1, wxString* sidebar_info = nullptr) const; + std::pair get_mesh_errors(wxString* sidebar_info = nullptr); void set_tooltip_for_item(const wxPoint& pt); void selection_changed(); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 6eaa6316d..7d5f80a30 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -497,14 +497,19 @@ void ObjectManipulation::update_ui_from_settings() int axis_id = 0; for (ManipulationEditor* editor : m_editors) { // editor->SetForegroundColour(m_use_colors ? wxColour(axes_color_text[axis_id]) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); -#ifdef _WIN32 - if (m_use_colors) + if (m_use_colors) { editor->SetBackgroundColour(wxColour(axes_color_back[axis_id])); - else + if (wxGetApp().dark_mode()) + editor->SetForegroundColour(*wxBLACK); + } + else { +#ifdef _WIN32 wxGetApp().UpdateDarkUI(editor); #else - editor->SetBackgroundColour(m_use_colors ? wxColour(axes_color_back[axis_id]) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + editor->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + editor->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); #endif /* _WIN32 */ + } editor->Refresh(); if (++axis_id == 3) axis_id = 0; @@ -1009,10 +1014,10 @@ void ObjectManipulation::sys_color_changed() get_og()->sys_color_changed(); wxGetApp().UpdateDarkUI(m_word_local_combo); wxGetApp().UpdateDarkUI(m_check_inch); - +#endif for (ManipulationEditor* editor : m_editors) editor->sys_color_changed(this); -#endif + // btn...->msw_rescale() updates icon on button, so use it m_mirror_bitmap_on.msw_rescale(); m_mirror_bitmap_off.msw_rescale(); @@ -1045,6 +1050,7 @@ ManipulationEditor::ManipulationEditor(ObjectManipulation* parent, #endif // __WXOSX__ if (parent->use_colors()) { this->SetBackgroundColour(wxColour(axes_color_back[axis])); + this->SetForegroundColour(*wxBLACK); } else { wxGetApp().UpdateDarkUI(this); } @@ -1097,10 +1103,14 @@ void ManipulationEditor::msw_rescale() void ManipulationEditor::sys_color_changed(ObjectManipulation* parent) { - if (!parent->use_colors()) - wxGetApp().UpdateDarkUI(this); - else + if (parent->use_colors()) SetForegroundColour(*wxBLACK); + else +#ifdef _WIN32 + wxGetApp().UpdateDarkUI(this); +#else + SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); +#endif // _WIN32 } double ManipulationEditor::get_value() diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 2133754bc..4d84454eb 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -748,7 +748,7 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee if( bottom_area - top_area > delta_area) { NotificationManager *notif_mngr = wxGetApp().plater()->get_notification_manager(); notif_mngr->push_notification( - NotificationType::SignDetected, NotificationManager::NotificationLevel::RegularNotificationLevel, + NotificationType::SignDetected, NotificationManager::NotificationLevel::PrintInfoNotificationLevel, _u8L("NOTE:") + "\n" + _u8L("Sliced object looks like the sign") + "\n", _u8L("Apply auto color change to print"), [this](wxEvtHandler*) { @@ -994,10 +994,9 @@ void Preview::load_print_as_fff(bool keep_z_range) if (0 <= type && type < static_cast(GCodeViewer::EViewType::Count)) { m_choice_view_type->SetSelection(type); m_canvas->set_gcode_view_preview_type(static_cast(type)); - if (wxGetApp().is_gcode_viewer()) { + if (wxGetApp().is_gcode_viewer()) m_keep_current_preview_type = true; - refresh_print(); - } + refresh_print(); } } #endif // ENABLE_PREVIEW_LAYOUT diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index c8142ba34..b14819671 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -182,7 +182,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit) double cut_z = m_cut_z; if (imperial_units) cut_z *= ObjectManipulation::mm_to_in; - ImGui::InputDouble("", &cut_z, 0.0f, 0.0f, "%.2f"); + ImGui::InputDouble("", &cut_z, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); ImGui::SameLine(); m_imgui->text(imperial_units ? _L("in") : _L("mm")); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index f0a627f90..d7824357f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -23,7 +23,7 @@ static inline void show_notification_extruders_limit_exceeded() wxGetApp() .plater() ->get_notification_manager() - ->push_notification(NotificationType::MmSegmentationExceededExtrudersLimit, NotificationManager::NotificationLevel::RegularNotificationLevel, + ->push_notification(NotificationType::MmSegmentationExceededExtrudersLimit, NotificationManager::NotificationLevel::PrintInfoNotificationLevel, GUI::format(_L("Your printer has more extruders than the multi-material painting gizmo supports. For this reason, only the " "first %1% extruders will be able to be used for painting."), GLGizmoMmuSegmentation::EXTRUDERS_LIMIT)); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 4fab1bcb6..c09d67317 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -60,8 +60,9 @@ void GLGizmoSimplify::on_render_for_picking() {} void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit) { create_gui_cfg(); - int obj_index; - ModelVolume *act_volume = get_selected_volume(&obj_index); + const Selection &selection = m_parent.get_selection(); + int obj_index = selection.get_object_idx(); + ModelVolume *act_volume = get_volume(selection, wxGetApp().plater()->model()); if (act_volume == nullptr) { switch (m_state) { case State::settings: close(); break; @@ -80,6 +81,10 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi set_its(*m_original_its); } + // close suggestion notification + auto notification_manager = wxGetApp().plater()->get_notification_manager(); + notification_manager->remove_simplify_suggestion_with_id(act_volume->get_object()->id()); + m_obj_index = obj_index; // to remember correct object m_volume = act_volume; m_original_its = {}; @@ -363,7 +368,7 @@ void GLGizmoSimplify::on_set_state() auto notification_manager = wxGetApp().plater()->get_notification_manager(); notification_manager->push_notification( NotificationType::CustomNotification, - NotificationManager::NotificationLevel::RegularNotificationLevel, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, _u8L("ERROR: Wait until Simplification ends or Cancel process.")); return; } @@ -423,17 +428,34 @@ bool GLGizmoSimplify::exist_volume(ModelVolume *volume) { return false; } -ModelVolume *GLGizmoSimplify::get_selected_volume(int *object_idx_ptr) const +ModelVolume * GLGizmoSimplify::get_volume(const Selection &selection, Model &model) { - const Selection &selection = m_parent.get_selection(); - int object_idx = selection.get_object_idx(); - if (object_idx_ptr != nullptr) *object_idx_ptr = object_idx; - if (object_idx < 0) return nullptr; - ModelObjectPtrs &objs = wxGetApp().plater()->model().objects; - if (static_cast(objs.size()) <= object_idx) return nullptr; - ModelObject *obj = objs[object_idx]; - if (obj->volumes.empty()) return nullptr; - return obj->volumes.front(); + const Selection::IndicesList& idxs = selection.get_volume_idxs(); + if (idxs.empty()) return nullptr; + // only one selected volume + if (idxs.size() != 1) return nullptr; + const GLVolume *selected_volume = selection.get_volume(*idxs.begin()); + if (selected_volume == nullptr) return nullptr; + + const GLVolume::CompositeID &cid = selected_volume->composite_id; + const ModelObjectPtrs& objs = model.objects; + if (cid.object_id < 0 || objs.size() <= static_cast(cid.object_id)) + return nullptr; + const ModelObject* obj = objs[cid.object_id]; + if (cid.volume_id < 0 || obj->volumes.size() <= static_cast(cid.volume_id)) + return nullptr; + return obj->volumes[cid.volume_id]; +} + +const ModelVolume *GLGizmoSimplify::get_volume(const GLVolume::CompositeID &cid, const Model &model) +{ + const ModelObjectPtrs &objs = model.objects; + if (cid.object_id < 0 || objs.size() <= static_cast(cid.object_id)) + return nullptr; + const ModelObject *obj = objs[cid.object_id]; + if (cid.volume_id < 0 || obj->volumes.size() <= static_cast(cid.volume_id)) + return nullptr; + return obj->volumes[cid.volume_id]; } void GLGizmoSimplify::init_wireframe() @@ -474,13 +496,17 @@ void GLGizmoSimplify::render_wireframe() const // is initialized? if (m_wireframe_VBO_id == 0 || m_wireframe_IBO_id == 0) return; if (!m_show_wireframe) return; - ModelVolume *act_volume = get_selected_volume(); - if (act_volume == nullptr) return; - const Transform3d trafo_matrix = - act_volume->get_object()->instances[m_parent.get_selection().get_instance_idx()] - ->get_transformation().get_matrix() * - act_volume->get_matrix(); + const auto& selection = m_parent.get_selection(); + const auto& volume_idxs = selection.get_volume_idxs(); + if (volume_idxs.empty() || volume_idxs.size() != 1) return; + const GLVolume *selected_volume = selection.get_volume(*volume_idxs.begin()); + + // check that selected model is wireframe initialized + if (m_volume != get_volume(selected_volume->composite_id, *m_parent.get_model())) + return; + + const Transform3d trafo_matrix = selected_volume->world_matrix(); glsafe(::glPushMatrix()); glsafe(::glMultMatrixd(trafo_matrix.data())); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index d2358b2d3..d624e3351 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -43,7 +43,9 @@ private: void set_its(indexed_triangle_set &its); void create_gui_cfg(); void request_rerender(); - ModelVolume *get_selected_volume(int *object_idx = nullptr) const; + // move to global functions + static ModelVolume *get_volume(const Selection &selection, Model &model); + static const ModelVolume *get_volume(const GLVolume::CompositeID &cid, const Model &model); // return false when volume was deleted static bool exist_volume(ModelVolume *volume); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 562226c2e..764c42c73 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -192,7 +192,7 @@ bool GLGizmosManager::check_gizmos_closed_except(EType type) const if (get_current_type() != type && get_current_type() != Undefined) { wxGetApp().plater()->get_notification_manager()->push_notification( NotificationType::CustomSupportsAndSeamRemovedAfterRepair, - NotificationManager::NotificationLevel::RegularNotificationLevel, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, _u8L("ERROR: Please close all manipulators available from " "the left toolbar first")); return false; diff --git a/src/slic3r/GUI/HintNotification.cpp b/src/slic3r/GUI/HintNotification.cpp index 53435e318..4f298eabf 100644 --- a/src/slic3r/GUI/HintNotification.cpp +++ b/src/slic3r/GUI/HintNotification.cpp @@ -5,10 +5,14 @@ #include "GUI_ObjectList.hpp" #include "GLCanvas3D.hpp" #include "MainFrame.hpp" +#include "Tab.hpp" #include "libslic3r/AppConfig.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Config.hpp" #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Preset.hpp" +#include "libslic3r/Config.hpp" +#include "libslic3r/PrintConfig.hpp" #include #include @@ -159,6 +163,33 @@ TagCheckResult tag_check_system(const std::string& tag) return TagCheckNotCompatible; } +TagCheckResult tag_check_material(const std::string& tag) +{ + if (const GUI::Tab* tab = wxGetApp().get_tab(Preset::Type::TYPE_FILAMENT)) { + // search PrintConfig filament_type to find if allowed tag + if (wxGetApp().app_config->get("filament_type").find(tag)) { + const Preset& preset = tab->m_presets->get_edited_preset(); + const auto* opt = preset.config.opt("filament_type"); + if (opt->values[0] == tag) + return TagCheckAffirmative; + return TagCheckNegative; + } + return TagCheckNotCompatible; + } + /* TODO: SLA materials + else if (const GUI::Tab* tab = wxGetApp().get_tab(Preset::Type::TYPE_SLA_MATERIAL)) { + //if (wxGetApp().app_config->get("material_type").find(tag)) { + const Preset& preset = tab->m_presets->get_edited_preset(); + const auto* opt = preset.config.opt("material_type"); + if (opt->values[0] == tag) + return TagCheckAffirmative; + return TagCheckNegative; + //} + return TagCheckNotCompatible; + }*/ + return TagCheckNotCompatible; +} + // return true if NOT in disabled mode. bool tags_check(const std::string& disabled_tags, const std::string& enabled_tags) { @@ -189,6 +220,11 @@ bool tags_check(const std::string& disabled_tags, const std::string& enabled_tag if (result == TagCheckResult::TagCheckAffirmative) continue; result = tag_check_system(tag); + if (result == TagCheckResult::TagCheckNegative) + return false; + if (result == TagCheckResult::TagCheckAffirmative) + continue; + result = tag_check_material(tag); if (result == TagCheckResult::TagCheckNegative) return false; if (result == TagCheckResult::TagCheckAffirmative) @@ -225,6 +261,11 @@ bool tags_check(const std::string& disabled_tags, const std::string& enabled_tag if (result == TagCheckResult::TagCheckAffirmative) return false; result = tag_check_system(tag); + if (result == TagCheckResult::TagCheckAffirmative) + return false; + if (result == TagCheckResult::TagCheckNegative) + continue; + result = tag_check_material(tag); if (result == TagCheckResult::TagCheckAffirmative) return false; if (result == TagCheckResult::TagCheckNegative) @@ -917,29 +958,14 @@ void NotificationManager::HintNotification::render_right_arrow_button(ImGuiWrapp } void NotificationManager::HintNotification::render_logo(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { - ImVec2 win_size(win_size_x, win_size_y); - ImVec2 win_pos(win_pos_x, win_pos_y); - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); - push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); - push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); - - std::wstring button_text; - button_text = ImGui::ClippyMarker;//LeftArrowButton; std::string placeholder_text; placeholder_text = ImGui::EjectButton; - ImVec2 button_pic_size = ImGui::CalcTextSize(placeholder_text.c_str()); - ImVec2 button_size(button_pic_size.x * 1.25f * 2.f, button_pic_size.y * 1.25f * 2.f); - ImGui::SetCursorPosY(win_size.y / 2 - button_size.y * 1.1f); - ImGui::SetCursorPosX(0); - // shouldnt it render as text? - if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) - { - } - - ImGui::PopStyleColor(5); + std::wstring text; + text = ImGui::ClippyMarker; + ImGui::SetCursorPosX(button_pic_size.x / 3); + ImGui::SetCursorPosY(win_size_y / 2 - button_pic_size.y * 2.f); + imgui.text(text.c_str()); } void NotificationManager::HintNotification::render_documentation_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { @@ -1012,7 +1038,7 @@ void NotificationManager::HintNotification::retrieve_data(bool new_hint/* = true if(hint_data != nullptr) { NotificationData nd { NotificationType::DidYouKnowHint, - NotificationLevel::RegularNotificationLevel, + NotificationLevel::HintNotificationLevel, 0, hint_data->text, hint_data->hypertext, nullptr, diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index e2bd87a2a..3ac61367f 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -48,6 +48,7 @@ static const std::map font_icons = { {ImGui::RightArrowHoverButton , "notification_right_hover" }, {ImGui::PreferencesButton , "notification_preferences" }, {ImGui::PreferencesHoverButton , "notification_preferences_hover"}, + }; static const std::map font_icons_large = { {ImGui::CloseNotifButton , "notification_close" }, @@ -65,6 +66,8 @@ static const std::map font_icons_large = { {ImGui::VarLayerHeightMarker , "layers" }, {ImGui::DocumentationButton , "notification_documentation" }, {ImGui::DocumentationHoverButton, "notification_documentation_hover"}, + {ImGui::InfoMarker , "notification_info" }, + }; static const std::map font_icons_extra_large = { @@ -392,7 +395,7 @@ bool ImGuiWrapper::image_button() bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format) { - return ImGui::InputDouble(label.c_str(), const_cast(&value), 0.0f, 0.0f, format.c_str()); + return ImGui::InputDouble(label.c_str(), const_cast(&value), 0.0f, 0.0f, format.c_str(), ImGuiInputTextFlags_CharsDecimal); } bool ImGuiWrapper::input_double(const wxString &label, const double &value, const std::string &format) diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index 3d611ffc3..c4465edba 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -153,8 +153,8 @@ void SLAImportJob::process() break; } } catch (MissingProfileError &) { - p->err = _L("The archive doesn't contain any profile data. Try to import after switching " - "to an SLA profile that can be used as fallback.").ToStdString(); + p->err = _L("The SLA archive doesn't contain any presets. " + "Please activate some SLA printer preset first before importing that SLA archive.").ToStdString(); } catch (std::exception &ex) { p->err = ex.what(); } @@ -207,8 +207,8 @@ void SLAImportJob::finalize() m_plater->get_notification_manager()->push_notification( NotificationType::CustomNotification, NotificationManager::NotificationLevel::WarningNotificationLevel, - _L("Loaded archive did not contain any profile data. " - "The current SLA profile was used as fallback.").ToStdString()); + _L("The imported SLA archive did not contain any presets. " + "The current SLA presets were used as fallback.").ToStdString()); } if (p->sel != Sel::modelOnly) { diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 79003f518..66e22c694 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -228,7 +228,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S return; } // check unsaved changes only if project wasn't saved - else if (saved_project == wxID_NO && event.CanVeto() && + else if (plater()->is_project_dirty() && saved_project == wxID_NO && event.CanVeto() && !wxGetApp().check_and_save_current_preset_changes(_L("PrusaSlicer is closing"), _L("Closing PrusaSlicer while some presets are modified."))) { event.Veto(); return; @@ -834,7 +834,7 @@ bool MainFrame::can_save() const #if ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED return (m_plater != nullptr) && !m_plater->canvas3D()->get_gizmos_manager().is_in_editing_mode(false) && - !m_plater->get_project_filename().empty() && m_plater->is_project_dirty(); + m_plater->is_project_dirty(); #else return (m_plater != nullptr) && !m_plater->model().objects.empty() && !m_plater->canvas3D()->get_gizmos_manager().is_in_editing_mode(false) && diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 98abb4863..3f98bd903 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -43,10 +43,10 @@ const NotificationManager::NotificationData NotificationManager::basic_notificat }, {NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) { wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }}, - {NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotificationLevel, 10, + {NotificationType::EmptyColorChangeCode, NotificationLevel::PrintInfoNotificationLevel, 10, _u8L("You have just added a G-code for color change, but its value is empty.\n" "To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") }, - {NotificationType::EmptyAutoColorChange, NotificationLevel::RegularNotificationLevel, 10, + {NotificationType::EmptyAutoColorChange, NotificationLevel::PrintInfoNotificationLevel, 10, _u8L("No color change event was added to the print. The print does not look like a sign.") }, {NotificationType::DesktopIntegrationSuccess, NotificationLevel::RegularNotificationLevel, 10, _u8L("Desktop integration was successful.") }, @@ -276,7 +276,9 @@ void NotificationManager::PopNotification::count_spaces() m_line_height = ImGui::CalcTextSize("A").y; m_left_indentation = m_line_height; - if (m_data.level == NotificationLevel::ErrorNotificationLevel || m_data.level == NotificationLevel::WarningNotificationLevel) { + if (m_data.level == NotificationLevel::ErrorNotificationLevel + || m_data.level == NotificationLevel::WarningNotificationLevel + || m_data.level == NotificationLevel::PrintInfoNotificationLevel) { std::string text; text = (m_data.level == NotificationLevel::ErrorNotificationLevel ? ImGui::ErrorMarker : ImGui::WarningMarker); float picture_width = ImGui::CalcTextSize(text.c_str()).x; @@ -373,7 +375,7 @@ void NotificationManager::PopNotification::init() void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui) { m_window_height = m_multiline ? - m_lines_count * m_line_height : + std::max(m_lines_count, (size_t)2) * m_line_height : 2 * m_line_height; m_window_height += 1 * m_line_height; // top and bottom } @@ -511,7 +513,13 @@ void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui) ImGui::SetCursorPosX(m_line_height / 3); ImGui::SetCursorPosY(m_window_height / 2 - m_line_height); imgui.text(text.c_str()); - } + } else if (m_data.level == NotificationLevel::PrintInfoNotificationLevel) { + std::wstring text; + text = ImGui::InfoMarker; + ImGui::SetCursorPosX(m_line_height / 3); + ImGui::SetCursorPosY(m_window_height / 2 - m_line_height); + imgui.text(text.c_str()); + } } void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) { @@ -1055,6 +1063,8 @@ void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType ty std::string text; for (it = m_types_and_counts.begin(); it != m_types_and_counts.end(); ++it) { + if ((*it).second == 0) + continue; text += std::to_string((*it).second); text += _L_PLURAL(" Object was loaded with "," Objects were loaded with ", (*it).second).ToUTF8().data(); switch ((*it).first) { @@ -1066,6 +1076,7 @@ void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType ty default: BOOST_LOG_TRIVIAL(error) << "Unknown InfoItemType: " << (*it).second; break; } } + m_state = EState::Unknown; NotificationData data { get_data().type, get_data().level , get_data().duration, text }; update(data); } @@ -1489,17 +1500,7 @@ void NotificationManager::push_notification(NotificationType type, std::function callback, int timestamp) { - int duration = 0; - switch (level) { - case NotificationLevel::RegularNotificationLevel: duration = 10; break; - case NotificationLevel::ErrorNotificationLevel: break; - case NotificationLevel::WarningNotificationLevel: break; - case NotificationLevel::ImportantNotificationLevel: break; - case NotificationLevel::ProgressBarNotificationLevel: break; - default: - assert(false); - return; - } + int duration = get_standart_duration(level); push_notification_data({ type, level, duration, text, hypertext, callback }, timestamp); } void NotificationManager::push_validate_error_notification(const std::string& text) @@ -1518,7 +1519,7 @@ void NotificationManager::push_slicing_warning_notification(const std::string& t { NotificationData data { NotificationType::SlicingWarning, NotificationLevel::WarningNotificationLevel, 0, _u8L("WARNING:") + "\n" + text }; - auto notification = std::make_unique(data, m_id_provider, m_evt_handler); + auto notification = std::make_unique(data, m_id_provider, m_evt_handler); notification->object_id = oid; notification->warning_step = warning_step; if (push_notification_data(std::move(notification), 0)) { @@ -1609,12 +1610,11 @@ void NotificationManager::close_slicing_error_notification(const std::string& te } } } -void NotificationManager::push_object_warning_notification(const std::string& text, ObjectID object_id, const std::string& hypertext/* = ""*/, std::function callback/* = std::function()*/) +void NotificationManager::push_simplify_suggestion_notification(const std::string& text, ObjectID object_id, const std::string& hypertext/* = ""*/, std::function callback/* = std::function()*/) { - NotificationData data{ NotificationType::ObjectWarning, NotificationLevel::WarningNotificationLevel, 0, text, hypertext, callback }; - auto notification = std::make_unique(data, m_id_provider, m_evt_handler); + NotificationData data{ NotificationType::SimplifySuggestion, NotificationLevel::PrintInfoNotificationLevel, 10, text, hypertext, callback }; + auto notification = std::make_unique(data, m_id_provider, m_evt_handler); notification->object_id = object_id; - notification->warning_step = 0; push_notification_data(std::move(notification), 0); } void NotificationManager::close_notification_of_type(const NotificationType type) @@ -1630,19 +1630,29 @@ void NotificationManager::remove_slicing_warnings_of_released_objects(const std: for (std::unique_ptr ¬ification : m_pop_notifications) if (notification->get_type() == NotificationType::SlicingWarning) { if (! std::binary_search(living_oids.begin(), living_oids.end(), - static_cast(notification.get())->object_id)) + static_cast(notification.get())->object_id)) notification->close(); } } -void NotificationManager::remove_object_warnings_of_released_objects(const std::vector& living_oids) +void NotificationManager::remove_simplify_suggestion_of_released_objects(const std::vector& living_oids) { for (std::unique_ptr& notification : m_pop_notifications) - if (notification->get_type() == NotificationType::ObjectWarning) { + if (notification->get_type() == NotificationType::SimplifySuggestion) { if (!std::binary_search(living_oids.begin(), living_oids.end(), - static_cast(notification.get())->object_id)) + static_cast(notification.get())->object_id)) notification->close(); } } + +void NotificationManager::remove_simplify_suggestion_with_id(const ObjectID oid) +{ + for (std::unique_ptr& notification : m_pop_notifications) + if (notification->get_type() == NotificationType::SimplifySuggestion) { + if (static_cast(notification.get())->object_id == oid) + notification->close(); + } +} + void NotificationManager::push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable) { close_notification_of_type(NotificationType::ExportFinished); @@ -1921,7 +1931,7 @@ void NotificationManager::push_updated_item_info_notification(InfoItemType type) } } - NotificationData data{ NotificationType::UpdatedItemsInfo, NotificationLevel::RegularNotificationLevel, 5, "" }; + NotificationData data{ NotificationType::UpdatedItemsInfo, NotificationLevel::PrintInfoNotificationLevel, 10, "" }; auto notification = std::make_unique(data, m_id_provider, m_evt_handler, type); if (push_notification_data(std::move(notification), 0)) { (dynamic_cast(m_pop_notifications.back().get()))->add_type(type); @@ -2084,8 +2094,8 @@ bool NotificationManager::activate_existing(const NotificationManager::PopNotifi continue; } } else if (new_type == NotificationType::SlicingWarning) { - auto w1 = dynamic_cast(notification); - auto w2 = dynamic_cast(it->get()); + auto w1 = dynamic_cast(notification); + auto w2 = dynamic_cast(it->get()); if (w1 != nullptr && w2 != nullptr) { if (!(*it)->compare_text(new_text) || w1->object_id != w2->object_id) { continue; diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 4481ed701..73f6a6340 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -71,9 +71,6 @@ enum class NotificationType PlaterError, // Object fully outside the print volume, or extrusion outside the print volume. Slicing is not disabled. PlaterWarning, - // Warning connected to single object id, appears at loading object, disapears at deletition. - // Example: advice to simplify object with big amount of triangles. - ObjectWarning, // Progress bar instead of text. ProgressBar, // Progress bar with info from Print Host Upload Queue dialog. @@ -105,7 +102,10 @@ enum class NotificationType // Might contain logo taken from gizmos UpdatedItemsInfo, // Progress bar notification with methods to replace ProgressIndicator class. - ProgressIndicator + ProgressIndicator, + // Give user advice to simplify object with big amount of triangles + // Contains ObjectID for closing when object is deleted + SimplifySuggestion }; class NotificationManager @@ -121,6 +121,8 @@ public: HintNotificationLevel, // "Good to know" notification, usually but not always with a quick fade-out. RegularNotificationLevel, + // Regular level notifiaction containing info about objects or print. Has Icon. + PrintInfoNotificationLevel, // Information notification without a fade-out or with a longer fade-out. ImportantNotificationLevel, // Warning, no fade-out. @@ -167,11 +169,12 @@ public: void close_plater_error_notification(const std::string& text); void close_plater_warning_notification(const std::string& text); // Object warning with ObjectID, closes when object is deleted. ID used is of object not print like in slicing warning. - void push_object_warning_notification(const std::string& text, ObjectID object_id, const std::string& hypertext = "", + void push_simplify_suggestion_notification(const std::string& text, ObjectID object_id, const std::string& hypertext = "", std::function callback = std::function()); // Close object warnings, whose ObjectID is not in the list. // living_oids is expected to be sorted. - void remove_object_warnings_of_released_objects(const std::vector& living_oids); + void remove_simplify_suggestion_of_released_objects(const std::vector& living_oids); + void remove_simplify_suggestion_with_id(const ObjectID oid); // Called when the side bar changes its visibility, as the "slicing complete" notification supplements // the "slicing info" normally shown at the side bar. void set_sidebar_collapsed(bool collapsed); @@ -212,6 +215,7 @@ public: bool is_hint_notification_open(); // Forces Hints to reload its content when next hint should be showed void deactivate_loaded_hints(); + // Adds counter to existing UpdatedItemsInfo notification or opens new one void push_updated_item_info_notification(InfoItemType type); // Close old notification ExportFinished. void new_export_began(bool on_removable); @@ -394,12 +398,14 @@ private: - class SlicingWarningNotification : public PopNotification + class ObjectIDNotification : public PopNotification { public: - SlicingWarningNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler) : PopNotification(n, id_provider, evt_handler) {} + ObjectIDNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler) + : PopNotification(n, id_provider, evt_handler) + {} ObjectID object_id; - int warning_step; + int warning_step { 0 }; }; class PlaterWarningNotification : public PopNotification @@ -649,6 +655,11 @@ private: } void count_spaces() override; void add_type(InfoItemType type); + void close() override{ + for (auto& tac : m_types_and_counts) + tac.second = 0; + PopNotification::close(); + } protected: void render_left_sign(ImGuiWrapper& imgui) override; std::vector> m_types_and_counts; @@ -691,7 +702,21 @@ private: void sort_notifications(); // If there is some error notification active, then the "Export G-code" notification after the slicing is finished is suppressed. bool has_slicing_error_notification(); - + size_t get_standart_duration(NotificationLevel level) + { + switch (level) { + + case NotificationLevel::ErrorNotificationLevel: return 0; + case NotificationLevel::WarningNotificationLevel: return 0; + case NotificationLevel::ImportantNotificationLevel: return 0; + case NotificationLevel::ProgressBarNotificationLevel: return 2; + case NotificationLevel::RegularNotificationLevel: return 10; + case NotificationLevel::PrintInfoNotificationLevel: return 10; + case NotificationLevel::HintNotificationLevel: return 300; + default: return 10; + } + } + // set by init(), until false notifications are only added not updated and frame is not requested after push bool m_initialized{ false }; // Target for wxWidgets events sent by clicking on the hyperlink available at some notifications. @@ -715,7 +740,7 @@ private: NotificationType::PlaterWarning, NotificationType::ProgressBar, NotificationType::PrintHostUpload, - NotificationType::ObjectWarning + NotificationType::SimplifySuggestion }; //prepared (basic) notifications static const NotificationData basic_notifications[]; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 92a8ecd92..95e63ee6d 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "libslic3r/libslic3r.h" #include "libslic3r/Format/STL.hpp" @@ -173,13 +174,10 @@ ObjectInfo::ObjectInfo(wxWindow *parent) : label_materials = init_info_label(&info_materials, _L("Materials")); Add(grid_sizer, 0, wxEXPAND); - auto *info_manifold_text = new wxStaticText(parent, wxID_ANY, _L("Manifold") + ":"); - info_manifold_text->SetFont(wxGetApp().small_font()); info_manifold = new wxStaticText(parent, wxID_ANY, ""); info_manifold->SetFont(wxGetApp().small_font()); manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap(m_warning_icon_name)); auto *sizer_manifold = new wxBoxSizer(wxHORIZONTAL); - sizer_manifold->Add(info_manifold_text, 0); sizer_manifold->Add(manifold_warning_icon, 0, wxLEFT, 2); sizer_manifold->Add(info_manifold, 0, wxLEFT, 2); Add(sizer_manifold, 0, wxEXPAND | wxTOP, 4); @@ -201,10 +199,10 @@ void ObjectInfo::msw_rescale() void ObjectInfo::update_warning_icon(const std::string& warning_icon_name) { - if (warning_icon_name.empty()) - return; - m_warning_icon_name = warning_icon_name; - manifold_warning_icon->SetBitmap(create_scaled_bitmap(m_warning_icon_name)); + if (showing_manifold_warning_icon = !warning_icon_name.empty()) { + m_warning_icon_name = warning_icon_name; + manifold_warning_icon->SetBitmap(create_scaled_bitmap(m_warning_icon_name)); + } } enum SlicedInfoIdx @@ -611,6 +609,7 @@ struct Sidebar::priv wxButton *btn_export_gcode; wxButton *btn_reslice; + wxString btn_reslice_tip; ScalableButton *btn_send_gcode; //ScalableButton *btn_eject_device; ScalableButton* btn_export_gcode_removable; //exports to removable drives (appears only if removable drive is connected) @@ -622,6 +621,7 @@ struct Sidebar::priv ~priv(); void show_preset_comboboxes(); + void show_rich_tip(const wxString& tooltip, wxButton* btn); }; Sidebar::priv::~priv() @@ -650,6 +650,18 @@ void Sidebar::priv::show_preset_comboboxes() scrolled->Refresh(); } +void Sidebar::priv::show_rich_tip(const wxString& tooltip, wxButton* btn) +{ + if (tooltip.IsEmpty()) + return; + wxRichToolTip tip(tooltip, ""); + tip.SetIcon(wxICON_NONE); + tip.SetTipKind(wxTipKind_BottomRight); + tip.SetTitleFont(wxGetApp().normal_font()); + tip.SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + tip.SetTimeout(1200); + tip.ShowFor(btn); +} // Sidebar / public @@ -784,7 +796,11 @@ Sidebar::Sidebar(Plater *parent) #endif //__APPLE__ ScalableBitmap bmp = ScalableBitmap(this, icon_name, bmp_px_cnt); *btn = new ScalableButton(this, wxID_ANY, bmp, "", wxBU_EXACTFIT); - (*btn)->SetToolTip(tooltip); + + (*btn)->Bind(wxEVT_ENTER_WINDOW, [tooltip, btn, this](wxMouseEvent& event) { + p->show_rich_tip(tooltip, *btn); + event.Skip(); + }); (*btn)->Hide(); }; @@ -843,6 +859,11 @@ Sidebar::Sidebar(Plater *parent) p->plater->reslice(); p->plater->select_view_3D("Preview"); }); + + p->btn_reslice->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& event) { + p->show_rich_tip(p->btn_reslice_tip, p->btn_reslice); + event.Skip(); + }); p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); }); // p->btn_eject_device->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->eject_drive(); }); p->btn_export_gcode_removable->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(true); }); @@ -970,7 +991,7 @@ void Sidebar::update_reslice_btn_tooltip() const wxString tooltip = wxString("Slice") + " [" + GUI::shortkey_ctrl_prefix() + "R]"; if (m_mode != comSimple) tooltip += wxString("\n") + _L("Hold Shift to Slice & Export G-code"); - p->btn_reslice->SetToolTip(tooltip); + p->btn_reslice_tip = tooltip; } void Sidebar::msw_rescale() @@ -1144,24 +1165,13 @@ void Sidebar::show_info_sizer() p->object_info->info_facets->SetLabel(format_wxstr(_L_PLURAL("%1% (%2$d shell)", "%1% (%2$d shells)", stats.number_of_parts), static_cast(model_object->facets_count()), stats.number_of_parts)); - if (stats.repaired()) { - int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + stats.facets_reversed + stats.backwards_edges; - p->object_info->info_manifold->SetLabel(format_wxstr(_L_PLURAL("Auto-repaired %1$d error", "Auto-repaired %1$d errors", errors), errors)); - - auto mesh_errors = obj_list()->get_mesh_errors(true); - wxString tooltip = mesh_errors.first; - - p->object_info->update_warning_icon(mesh_errors.second); - p->object_info->showing_manifold_warning_icon = true; - p->object_info->info_manifold->SetToolTip(tooltip); - p->object_info->manifold_warning_icon->SetToolTip(tooltip); - } - else { - p->object_info->info_manifold->SetLabel(_L("Yes")); - p->object_info->showing_manifold_warning_icon = false; - p->object_info->info_manifold->SetToolTip(""); - p->object_info->manifold_warning_icon->SetToolTip(""); - } + wxString info_manifold_label; + auto mesh_errors = obj_list()->get_mesh_errors(&info_manifold_label); + wxString tooltip = mesh_errors.first; + p->object_info->update_warning_icon(mesh_errors.second); + p->object_info->info_manifold->SetLabel(info_manifold_label); + p->object_info->info_manifold->SetToolTip(tooltip); + p->object_info->manifold_warning_icon->SetToolTip(tooltip); p->object_info->show_sizer(true); @@ -2111,13 +2121,14 @@ void Plater::priv::update(unsigned int flags) } unsigned int update_status = 0; - if (this->printer_technology == ptSLA || (flags & (unsigned int)UpdateParams::FORCE_BACKGROUND_PROCESSING_UPDATE)) + const bool force_background_processing_restart = this->printer_technology == ptSLA || (flags & (unsigned int)UpdateParams::FORCE_BACKGROUND_PROCESSING_UPDATE); + if (force_background_processing_restart) // Update the SLAPrint from the current Model, so that the reload_scene() // pulls the correct data. update_status = this->update_background_process(false, flags & (unsigned int)UpdateParams::POSTPONE_VALIDATION_ERROR_MESSAGE); this->view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH); this->preview->reload_print(); - if (this->printer_technology == ptSLA) + if (force_background_processing_restart) this->restart_background_process(update_status); else this->schedule_background_process(); @@ -2348,7 +2359,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ for (std::string& name : names) notif_text += "\n - " + name; notification_manager->push_notification(NotificationType::CustomNotification, - NotificationManager::NotificationLevel::RegularNotificationLevel, notif_text); + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, notif_text); } } @@ -2444,7 +2455,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ for (ModelObject* model_object : model.objects) { if (!type_3mf && !type_zip_amf) model_object->center_around_origin(false); - model_object->ensure_on_bed(is_project_file || type_3mf || type_zip_amf); + model_object->ensure_on_bed(is_project_file); } // check multi-part object adding for the SLA-printing @@ -2461,7 +2472,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (one_by_one) { if (type_3mf && !is_project_file) model.center_instances_around_point(bed_shape_bb().center()); - auto loaded_idxs = load_model_objects(model.objects, is_project_file || type_3mf || type_zip_amf); + auto loaded_idxs = load_model_objects(model.objects, is_project_file); obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); } else { // This must be an .stl or .obj file, which may contain a maximum of one volume. @@ -2602,6 +2613,7 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs& mode // so 3D-scene should be updated before object additing to the ObjectList this->view3D->reload_scene(false, (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH); + notification_manager->close_notification_of_type(NotificationType::UpdatedItemsInfo); for (const size_t idx : obj_idxs) { wxGetApp().obj_list()->add_object_to_list(idx); } @@ -2911,7 +2923,7 @@ void Plater::priv::split_object() // If we splited object which is contain some parts/modifiers then all non-solid parts (modifiers) were deleted if (current_model_object->volumes.size() > 1 && current_model_object->volumes.size() != new_objects.size()) notification_manager->push_notification(NotificationType::CustomNotification, - NotificationManager::NotificationLevel::RegularNotificationLevel, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, _u8L("All non-solid parts (modifiers) were deleted")); Plater::TakeSnapshot snapshot(q, _L("Split to Objects")); @@ -3081,10 +3093,13 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool //actualizate warnings if (invalidated != Print::APPLY_STATUS_UNCHANGED) { + if (background_process.empty()) + process_validation_warning(std::string()); actualize_slicing_warnings(*this->background_process.current_print()); actualize_object_warnings(*this->background_process.current_print()); show_warning_dialog = false; process_completed_with_error = false; + } if (invalidated != Print::APPLY_STATUS_UNCHANGED && was_running && ! this->background_process.running() && @@ -3713,8 +3728,8 @@ void Plater::priv::create_simplify_notification(const std::vector& obj_i "amount of triangles."); t.replace(t.find("@object_name"), sizeof("@object_name") - 1, model.objects[object_id]->name); - std::stringstream text; - text << _u8L("WARNING:") << "\n" << t << "\n"; + //std::stringstream text; + //text << t << "\n"; std::string hypertext = _u8L("Simplify model"); std::function open_simplify = [object_id](wxEvtHandler *) { @@ -3729,7 +3744,10 @@ void Plater::priv::create_simplify_notification(const std::vector& obj_i manager.open_gizmo(GLGizmosManager::EType::Simplify); return true; }; - notification_manager->push_object_warning_notification(text.str(), model.objects[object_id]->id(), hypertext, open_simplify); + notification_manager->push_simplify_suggestion_notification(t, + model.objects[object_id]->id(), + hypertext, + open_simplify); } } @@ -4002,7 +4020,7 @@ void Plater::priv::actualize_object_warnings(const PrintBase& print) ids.push_back(object->id()); } std::sort(ids.begin(), ids.end()); - notification_manager->remove_object_warnings_of_released_objects(ids); + notification_manager->remove_simplify_suggestion_of_released_objects(ids); } void Plater::priv::clear_warnings() { @@ -4167,12 +4185,14 @@ void Plater::priv::on_right_click(RBtnEvent& evt) if (printer_technology == ptSLA) menu = menus.sla_object_menu(); else { + const Selection& selection = get_selection(); // show "Object menu" for each one or several FullInstance instead of FullObject - const bool is_some_full_instances = get_selection().is_single_full_instance() || - get_selection().is_single_full_object() || - get_selection().is_multiple_full_instance(); - menu = is_some_full_instances ? menus.object_menu() : - get_selection().is_single_volume() ? menus.part_menu() : menus.multi_selection_menu(); + const bool is_some_full_instances = selection.is_single_full_instance() || + selection.is_single_full_object() || + selection.is_multiple_full_instance(); + const bool is_part = selection.is_single_volume() || selection.is_single_modifier(); + menu = is_some_full_instances ? menus.object_menu() : + is_part ? menus.part_menu() : menus.multi_selection_menu(); } } @@ -4533,6 +4553,11 @@ bool Plater::priv::can_fix_through_netfabb() const std::vector obj_idxs, vol_idxs; sidebar->obj_list()->get_selection_indexes(obj_idxs, vol_idxs); +#if FIX_THROUGH_NETFABB_ALWAYS + // Fixing always. + return ! obj_idxs.empty() || ! vol_idxs.empty(); +#else // FIX_THROUGH_NETFABB_ALWAYS + // Fixing only if the model is not manifold. if (vol_idxs.empty()) { for (auto obj_idx : obj_idxs) if (model.objects[obj_idx]->get_mesh_errors_count() > 0) @@ -4544,11 +4569,10 @@ bool Plater::priv::can_fix_through_netfabb() const for (auto vol_idx : vol_idxs) if (model.objects[obj_idx]->get_mesh_errors_count(vol_idx) > 0) return true; - return false; +#endif // FIX_THROUGH_NETFABB_ALWAYS } - bool Plater::priv::can_simplify() const { // is object for simplification selected @@ -6425,7 +6449,7 @@ void Plater::clear_before_change_mesh(int obj_idx) // snapshot_time is captured by copy so the lambda knows where to undo/redo to. get_notification_manager()->push_notification( NotificationType::CustomSupportsAndSeamRemovedAfterRepair, - NotificationManager::NotificationLevel::RegularNotificationLevel, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, _u8L("Custom supports, seams and multimaterial painting were " "removed after repairing the mesh.")); // _u8L("Undo the repair"), diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index c91ac4784..8b3cd4cf8 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3236,7 +3236,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, const PresetWithVendorProfile new_printer_preset_with_vendor_profile = m_presets->get_preset_with_vendor_profile(new_printer_preset); PrinterTechnology old_printer_technology = m_presets->get_edited_preset().printer_technology(); PrinterTechnology new_printer_technology = new_printer_preset.printer_technology(); - if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !may_switch_to_SLA_preset()) + if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !wxGetApp().may_switch_to_SLA_preset(_L("New printer preset is selecting"))) canceled = true; else { struct PresetUpdate { @@ -3409,21 +3409,6 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr return true; } -// If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s). -// Because of we can't to print the multi-part objects with SLA technology. -bool Tab::may_switch_to_SLA_preset() -{ - if (model_has_multi_part_objects(wxGetApp().model())) - { - show_info( parent(), - _(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" + - _(L("Please check your object list before preset changing.")), - _(L("Attention!")) ); - return false; - } - return true; -} - void Tab::clear_pages() { // invalidated highlighter, if any exists diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 5b7983711..6a7b56fe4 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -282,7 +282,6 @@ public: // Select a new preset, possibly delete the current one. void select_preset(std::string preset_name = "", bool delete_current = false, const std::string& last_selected_ph_printer_name = ""); bool may_discard_current_dirty_preset(PresetCollection* presets = nullptr, const std::string& new_printer_name = ""); - bool may_switch_to_SLA_preset(); virtual void clear_pages(); virtual void update_description_lines(); diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 575878cf2..05898db28 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -23,6 +23,7 @@ add_executable(${_TEST_NAME}_tests test_png_io.cpp test_timeutils.cpp test_indexed_triangle_set.cpp + ../libnest2d/printer_parts.cpp ) if (TARGET OpenVDB::openvdb) diff --git a/tests/libslic3r/test_geometry.cpp b/tests/libslic3r/test_geometry.cpp index 24e0908cc..b183de1b4 100644 --- a/tests/libslic3r/test_geometry.cpp +++ b/tests/libslic3r/test_geometry.cpp @@ -9,6 +9,14 @@ #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/ShortestPath.hpp" +//#include +//#include "libnest2d/tools/benchmark.h" +#include "libslic3r/SVG.hpp" + +#include "../libnest2d/printer_parts.hpp" + +#include + using namespace Slic3r; TEST_CASE("Polygon::contains works properly", "[Geometry]"){ @@ -452,3 +460,225 @@ SCENARIO("Ported from xs/t/14_geometry.t", "[Geometry]"){ REQUIRE(! Slic3r::Geometry::directions_parallel(M_PI /2, PI, M_PI /180)); } } + +TEST_CASE("Convex polygon intersection on two disjoint squares", "[Geometry][Rotcalip]") { + Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}}; + A.scale(1. / SCALING_FACTOR); + + Polygon B = A; + B.translate(20 / SCALING_FACTOR, 0); + + bool is_inters = Geometry::intersects(A, B); + + REQUIRE(is_inters == false); +} + +TEST_CASE("Convex polygon intersection on two intersecting squares", "[Geometry][Rotcalip]") { + Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}}; + A.scale(1. / SCALING_FACTOR); + + Polygon B = A; + B.translate(5 / SCALING_FACTOR, 5 / SCALING_FACTOR); + + bool is_inters = Geometry::intersects(A, B); + + REQUIRE(is_inters == true); +} + +TEST_CASE("Convex polygon intersection on two squares touching one edge", "[Geometry][Rotcalip]") { + Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}}; + A.scale(1. / SCALING_FACTOR); + + Polygon B = A; + B.translate(10 / SCALING_FACTOR, 0); + + bool is_inters = Geometry::intersects(A, B); + + REQUIRE(is_inters == false); +} + +TEST_CASE("Convex polygon intersection on two squares touching one vertex", "[Geometry][Rotcalip]") { + Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}}; + A.scale(1. / SCALING_FACTOR); + + Polygon B = A; + B.translate(10 / SCALING_FACTOR, 10 / SCALING_FACTOR); + + SVG svg{std::string("one_vertex_touch") + ".svg"}; + svg.draw(A, "blue"); + svg.draw(B, "green"); + svg.Close(); + + bool is_inters = Geometry::intersects(A, B); + + REQUIRE(is_inters == false); +} + +TEST_CASE("Convex polygon intersection on two overlapping squares", "[Geometry][Rotcalip]") { + Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}}; + A.scale(1. / SCALING_FACTOR); + + Polygon B = A; + + bool is_inters = Geometry::intersects(A, B); + + REQUIRE(is_inters == true); +} + +//// Only for benchmarking +//static Polygon gen_convex_poly(std::mt19937_64 &rg, size_t point_cnt) +//{ +// std::uniform_int_distribution dist(0, 100); + +// Polygon out; +// out.points.reserve(point_cnt); + +// coord_t tr = dist(rg) * 2 / SCALING_FACTOR; + +// for (size_t i = 0; i < point_cnt; ++i) +// out.points.emplace_back(tr + dist(rg) / SCALING_FACTOR, +// tr + dist(rg) / SCALING_FACTOR); + +// return Geometry::convex_hull(out.points); +//} +//TEST_CASE("Convex polygon intersection test on random polygons", "[Geometry]") { +// constexpr size_t TEST_CNT = 1000; +// constexpr size_t POINT_CNT = 1000; + +// auto seed = std::random_device{}(); +//// unsigned long seed = 2525634386; +// std::mt19937_64 rg{seed}; +// Benchmark bench; + +// auto tests = reserve_vector>(TEST_CNT); +// auto results = reserve_vector(TEST_CNT); +// auto expects = reserve_vector(TEST_CNT); + +// for (size_t i = 0; i < TEST_CNT; ++i) { +// tests.emplace_back(gen_convex_poly(rg, POINT_CNT), gen_convex_poly(rg, POINT_CNT)); +// } + +// bench.start(); +// for (const auto &test : tests) +// results.emplace_back(Geometry::intersects(test.first, test.second)); +// bench.stop(); + +// std::cout << "Test time: " << bench.getElapsedSec() << std::endl; + +// bench.start(); +// for (const auto &test : tests) +// expects.emplace_back(!intersection(test.first, test.second).empty()); +// bench.stop(); + +// std::cout << "Clipper time: " << bench.getElapsedSec() << std::endl; + +// REQUIRE(results.size() == expects.size()); + +// auto seedstr = std::to_string(seed); +// for (size_t i = 0; i < results.size(); ++i) { +// // std::cout << expects[i] << " "; + +// if (results[i] != expects[i]) { +// SVG svg{std::string("fail_seed") + seedstr + "_" + std::to_string(i) + ".svg"}; +// svg.draw(tests[i].first, "blue"); +// svg.draw(tests[i].second, "green"); +// svg.Close(); + +// // std::cout << std::endl; +// } +// REQUIRE(results[i] == expects[i]); +// } +// std::cout << std::endl; + +//} + +struct Pair +{ + size_t first, second; + bool operator==(const Pair &b) const { return first == b.first && second == b.second; } +}; + +template<> struct std::hash { + size_t operator()(const Pair &c) const + { + return c.first * PRINTER_PART_POLYGONS.size() + c.second; + } +}; + +TEST_CASE("Convex polygon intersection test prusa polygons", "[Geometry][Rotcalip]") { + + // Overlap of the same polygon should always be an intersection + for (size_t i = 0; i < PRINTER_PART_POLYGONS.size(); ++i) { + Polygon P = PRINTER_PART_POLYGONS[i]; + P = Geometry::convex_hull(P.points); + bool res = Geometry::intersects(P, P); + if (!res) { + SVG svg{std::string("fail_self") + std::to_string(i) + ".svg"}; + svg.draw(P, "green"); + svg.Close(); + } + REQUIRE(res == true); + } + + std::unordered_set combos; + for (size_t i = 0; i < PRINTER_PART_POLYGONS.size(); ++i) { + for (size_t j = 0; j < PRINTER_PART_POLYGONS.size(); ++j) { + if (i != j) { + size_t a = std::min(i, j), b = std::max(i, j); + combos.insert(Pair{a, b}); + } + } + } + + // All disjoint + for (const auto &combo : combos) { + Polygon A = PRINTER_PART_POLYGONS[combo.first], B = PRINTER_PART_POLYGONS[combo.second]; + A = Geometry::convex_hull(A.points); + B = Geometry::convex_hull(B.points); + + auto bba = A.bounding_box(); + auto bbb = B.bounding_box(); + + A.translate(-bba.center()); + B.translate(-bbb.center()); + + B.translate(bba.size() + bbb.size()); + + bool res = Geometry::intersects(A, B); + bool ref = !intersection(A, B).empty(); + + if (res != ref) { + SVG svg{std::string("fail") + std::to_string(combo.first) + "_" + std::to_string(combo.second) + ".svg"}; + svg.draw(A, "blue"); + svg.draw(B, "green"); + svg.Close(); + } + + REQUIRE(res == ref); + } + + // All intersecting + for (const auto &combo : combos) { + Polygon A = PRINTER_PART_POLYGONS[combo.first], B = PRINTER_PART_POLYGONS[combo.second]; + A = Geometry::convex_hull(A.points); + B = Geometry::convex_hull(B.points); + + auto bba = A.bounding_box(); + auto bbb = B.bounding_box(); + + A.translate(-bba.center()); + B.translate(-bbb.center()); + + bool res = Geometry::intersects(A, B); + bool ref = !intersection(A, B).empty(); + + if (res != ref) { + SVG svg{std::string("fail") + std::to_string(combo.first) + "_" + std::to_string(combo.second) + ".svg"}; + svg.draw(A, "blue"); + svg.draw(B, "green"); + svg.Close(); + } + + REQUIRE(res == ref); + } +} diff --git a/version.inc b/version.inc index b7e941bb4..fdbc2523f 100644 --- a/version.inc +++ b/version.inc @@ -3,7 +3,7 @@ set(SLIC3R_APP_NAME "PrusaSlicer") set(SLIC3R_APP_KEY "PrusaSlicer") -set(SLIC3R_VERSION "2.4.0-alpha2") +set(SLIC3R_VERSION "2.4.0-alpha3") set(SLIC3R_BUILD_ID "PrusaSlicer-${SLIC3R_VERSION}+UNKNOWN") set(SLIC3R_RC_VERSION "2,4,0,0") set(SLIC3R_RC_VERSION_DOTS "2.4.0.0")