mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-30 12:11:15 -06:00 
			
		
		
		
	Fixed conflicts after merge with master
This commit is contained in:
		
						commit
						25c3552555
					
				
					 68 changed files with 4042 additions and 1798 deletions
				
			
		|  | @ -113,7 +113,12 @@ namespace ImGui | |||
|     const char PrinterSlaIconMarker = 0x6;  | ||||
|     const char FilamentIconMarker   = 0x7;  | ||||
|     const char MaterialIconMarker   = 0x8; | ||||
| 
 | ||||
| 	const char CloseIconMarker      = 0xB; | ||||
| 	const char CloseIconHoverMarker = 0xC; | ||||
| 	const char TimerDotMarker       = 0xE; | ||||
| 	const char TimerDotEmptyMarker  = 0xF; | ||||
| 	const char WarningMarker        = 0x10; | ||||
| 	const char ErrorMarker          = 0x11; | ||||
| //    void MyFunction(const char* name, const MyMatrix44& v);
 | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -205,12 +205,13 @@ add_library(libslic3r STATIC | |||
|     SimplifyMeshImpl.hpp | ||||
|     SimplifyMesh.cpp | ||||
|     MarchingSquares.hpp | ||||
|     Optimizer.hpp | ||||
|     ${OpenVDBUtils_SOURCES} | ||||
|     SLA/Common.hpp | ||||
|     SLA/Common.cpp | ||||
|     SLA/Pad.hpp | ||||
|     SLA/Pad.cpp | ||||
|     SLA/SupportTreeBuilder.hpp | ||||
|     SLA/SupportTreeMesher.hpp | ||||
|     SLA/SupportTreeMesher.cpp | ||||
|     SLA/SupportTreeBuildsteps.hpp | ||||
|     SLA/SupportTreeBuildsteps.cpp | ||||
|     SLA/SupportTreeBuilder.cpp | ||||
|  | @ -222,6 +223,7 @@ add_library(libslic3r STATIC | |||
|     SLA/Rotfinder.cpp | ||||
|     SLA/BoostAdapter.hpp | ||||
|     SLA/SpatIndex.hpp | ||||
|     SLA/SpatIndex.cpp | ||||
|     SLA/RasterBase.hpp | ||||
|     SLA/RasterBase.cpp | ||||
|     SLA/AGGRaster.hpp | ||||
|  | @ -237,8 +239,10 @@ add_library(libslic3r STATIC | |||
|     SLA/SupportPointGenerator.cpp | ||||
|     SLA/Contour3D.hpp | ||||
|     SLA/Contour3D.cpp | ||||
|     SLA/EigenMesh3D.hpp | ||||
|     SLA/IndexedMesh.hpp | ||||
|     SLA/IndexedMesh.cpp | ||||
|     SLA/Clustering.hpp | ||||
|     SLA/Clustering.cpp | ||||
|     SLA/ReprojectPointsOnMesh.hpp | ||||
| ) | ||||
| 
 | ||||
|  |  | |||
|  | @ -691,6 +691,7 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec | |||
|     std::sort(ordering.begin(), ordering.end(), [](const OrderingItem& oi1, const OrderingItem& oi2) { return oi1.print_z < oi2.print_z; }); | ||||
| 
 | ||||
|     std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print; | ||||
| 
 | ||||
|     // Merge numerically very close Z values.
 | ||||
|     for (size_t i = 0; i < ordering.size();) { | ||||
|         // Find the last layer with roughly the same print_z.
 | ||||
|  |  | |||
|  | @ -2,7 +2,6 @@ | |||
| #define OPENVDBUTILS_HPP | ||||
| 
 | ||||
| #include <libslic3r/TriangleMesh.hpp> | ||||
| #include <libslic3r/SLA/Common.hpp> | ||||
| #include <libslic3r/SLA/Contour3D.hpp> | ||||
| #include <openvdb/openvdb.h> | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										380
									
								
								src/libslic3r/Optimizer.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										380
									
								
								src/libslic3r/Optimizer.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,380 @@ | |||
| #ifndef NLOPTOPTIMIZER_HPP | ||||
| #define NLOPTOPTIMIZER_HPP | ||||
| 
 | ||||
| #ifdef _MSC_VER | ||||
| #pragma warning(push) | ||||
| #pragma warning(disable: 4244) | ||||
| #pragma warning(disable: 4267) | ||||
| #endif | ||||
| #include <nlopt.h> | ||||
| #ifdef _MSC_VER | ||||
| #pragma warning(pop) | ||||
| #endif | ||||
| 
 | ||||
| #include <utility> | ||||
| #include <tuple> | ||||
| #include <array> | ||||
| #include <cmath> | ||||
| #include <functional> | ||||
| #include <limits> | ||||
| #include <cassert> | ||||
| 
 | ||||
| namespace Slic3r { namespace opt { | ||||
| 
 | ||||
| // A type to hold the complete result of the optimization.
 | ||||
| template<size_t N> struct Result { | ||||
|     int resultcode; | ||||
|     std::array<double, N> optimum; | ||||
|     double score; | ||||
| }; | ||||
| 
 | ||||
| // An interval of possible input values for optimization
 | ||||
| class Bound { | ||||
|     double m_min, m_max; | ||||
| 
 | ||||
| public: | ||||
|     Bound(double min = std::numeric_limits<double>::min(), | ||||
|           double max = std::numeric_limits<double>::max()) | ||||
|         : m_min(min), m_max(max) | ||||
|     {} | ||||
| 
 | ||||
|     double min() const noexcept { return m_min; } | ||||
|     double max() const noexcept { return m_max; } | ||||
| }; | ||||
| 
 | ||||
| // Helper types for optimization function input and bounds
 | ||||
| template<size_t N> using Input = std::array<double, N>; | ||||
| template<size_t N> using Bounds = std::array<Bound, N>; | ||||
| 
 | ||||
| // A type for specifying the stop criteria. Setter methods can be concatenated
 | ||||
| class StopCriteria { | ||||
| 
 | ||||
|     // If the absolute value difference between two scores.
 | ||||
|     double m_abs_score_diff = std::nan(""); | ||||
| 
 | ||||
|     // If the relative value difference between two scores.
 | ||||
|     double m_rel_score_diff = std::nan(""); | ||||
| 
 | ||||
|     // Stop if this value or better is found.
 | ||||
|     double m_stop_score = std::nan(""); | ||||
| 
 | ||||
|     // A predicate that if evaluates to true, the optimization should terminate
 | ||||
|     // and the best result found prior to termination should be returned.
 | ||||
|     std::function<bool()> m_stop_condition = [] { return false; }; | ||||
| 
 | ||||
|     // The max allowed number of iterations.
 | ||||
|     unsigned m_max_iterations = 0; | ||||
| 
 | ||||
| public: | ||||
| 
 | ||||
|     StopCriteria & abs_score_diff(double val) | ||||
|     { | ||||
|         m_abs_score_diff = val; return *this; | ||||
|     } | ||||
| 
 | ||||
|     double abs_score_diff() const { return m_abs_score_diff; } | ||||
| 
 | ||||
|     StopCriteria & rel_score_diff(double val) | ||||
|     { | ||||
|         m_rel_score_diff = val; return *this; | ||||
|     } | ||||
| 
 | ||||
|     double rel_score_diff() const { return m_rel_score_diff; } | ||||
| 
 | ||||
|     StopCriteria & stop_score(double val) | ||||
|     { | ||||
|         m_stop_score = val; return *this; | ||||
|     } | ||||
| 
 | ||||
|     double stop_score() const { return m_stop_score; } | ||||
| 
 | ||||
|     StopCriteria & max_iterations(double val) | ||||
|     { | ||||
|         m_max_iterations = val; return *this; | ||||
|     } | ||||
| 
 | ||||
|     double max_iterations() const { return m_max_iterations; } | ||||
| 
 | ||||
|     template<class Fn> StopCriteria & stop_condition(Fn &&cond) | ||||
|     { | ||||
|         m_stop_condition = cond; return *this; | ||||
|     } | ||||
| 
 | ||||
|     bool stop_condition() { return m_stop_condition(); } | ||||
| }; | ||||
| 
 | ||||
| // Helper class to use optimization methods involving gradient.
 | ||||
| template<size_t N> struct ScoreGradient { | ||||
|     double score; | ||||
|     std::optional<std::array<double, N>> gradient; | ||||
| 
 | ||||
|     ScoreGradient(double s, const std::array<double, N> &grad) | ||||
|         : score{s}, gradient{grad} | ||||
|     {} | ||||
| }; | ||||
| 
 | ||||
| // Helper to be used in static_assert.
 | ||||
| template<class T> struct always_false { enum { value = false }; }; | ||||
| 
 | ||||
| // Basic interface to optimizer object
 | ||||
| template<class Method, class Enable = void> class Optimizer { | ||||
| public: | ||||
| 
 | ||||
|     Optimizer(const StopCriteria &) | ||||
|     { | ||||
|         static_assert (always_false<Method>::value, | ||||
|                        "Optimizer unimplemented for given method!"); | ||||
|     } | ||||
| 
 | ||||
|     Optimizer<Method> &to_min() { return *this; } | ||||
|     Optimizer<Method> &to_max() { return *this; } | ||||
|     Optimizer<Method> &set_criteria(const StopCriteria &) { return *this; } | ||||
|     StopCriteria get_criteria() const { return {}; }; | ||||
| 
 | ||||
|     template<class Func, size_t N> | ||||
|     Result<N> optimize(Func&& func, | ||||
|                        const Input<N> &initvals, | ||||
|                        const Bounds<N>& bounds) { return {}; } | ||||
| 
 | ||||
|     // optional for randomized methods:
 | ||||
|     void seed(long /*s*/) {} | ||||
| }; | ||||
| 
 | ||||
| namespace detail { | ||||
| 
 | ||||
| // Helper types for NLopt algorithm selection in template contexts
 | ||||
| template<nlopt_algorithm alg> struct NLoptAlg {}; | ||||
| 
 | ||||
| // NLopt can combine multiple algorithms if one is global an other is a local
 | ||||
| // method. This is how template specializations can be informed about this fact.
 | ||||
| template<nlopt_algorithm gl_alg, nlopt_algorithm lc_alg = NLOPT_LN_NELDERMEAD> | ||||
| struct NLoptAlgComb {}; | ||||
| 
 | ||||
| template<class M> struct IsNLoptAlg { | ||||
|     static const constexpr bool value = false; | ||||
| }; | ||||
| 
 | ||||
| template<nlopt_algorithm a> struct IsNLoptAlg<NLoptAlg<a>> { | ||||
|     static const constexpr bool value = true; | ||||
| }; | ||||
| 
 | ||||
| template<nlopt_algorithm a1, nlopt_algorithm a2> | ||||
| struct IsNLoptAlg<NLoptAlgComb<a1, a2>> { | ||||
|     static const constexpr bool value = true; | ||||
| }; | ||||
| 
 | ||||
| template<class M, class T = void> | ||||
| using NLoptOnly = std::enable_if_t<IsNLoptAlg<M>::value, T>; | ||||
| 
 | ||||
| // Helper to convert C style array to std::array. The copy should be optimized
 | ||||
| // away with modern compilers.
 | ||||
| template<size_t N, class T> auto to_arr(const T *a) | ||||
| { | ||||
|     std::array<T, N> r; | ||||
|     std::copy(a, a + N, std::begin(r)); | ||||
|     return r; | ||||
| } | ||||
| 
 | ||||
| template<size_t N, class T> auto to_arr(const T (&a) [N]) | ||||
| { | ||||
|     return to_arr<N>(static_cast<const T *>(a)); | ||||
| } | ||||
| 
 | ||||
| enum class OptDir { MIN, MAX }; // Where to optimize
 | ||||
| 
 | ||||
| struct NLopt { // Helper RAII class for nlopt_opt
 | ||||
|     nlopt_opt ptr = nullptr; | ||||
| 
 | ||||
|     template<class...A> explicit NLopt(A&&...a) | ||||
|     { | ||||
|         ptr = nlopt_create(std::forward<A>(a)...); | ||||
|     } | ||||
| 
 | ||||
|     NLopt(const NLopt&) = delete; | ||||
|     NLopt(NLopt&&) = delete; | ||||
|     NLopt& operator=(const NLopt&) = delete; | ||||
|     NLopt& operator=(NLopt&&) = delete; | ||||
| 
 | ||||
|     ~NLopt() { nlopt_destroy(ptr); } | ||||
| }; | ||||
| 
 | ||||
| template<class Method> class NLoptOpt {}; | ||||
| 
 | ||||
| // Optimizers based on NLopt.
 | ||||
| template<nlopt_algorithm alg> class NLoptOpt<NLoptAlg<alg>> { | ||||
| protected: | ||||
|     StopCriteria m_stopcr; | ||||
|     OptDir m_dir; | ||||
| 
 | ||||
|     template<class Fn> using TOptData = | ||||
|         std::tuple<std::remove_reference_t<Fn>*, NLoptOpt*, nlopt_opt>; | ||||
| 
 | ||||
|     template<class Fn, size_t N> | ||||
|     static double optfunc(unsigned n, const double *params, | ||||
|                           double *gradient, | ||||
|                           void *data) | ||||
|     { | ||||
|         assert(n >= N); | ||||
| 
 | ||||
|         auto tdata = static_cast<TOptData<Fn>*>(data); | ||||
| 
 | ||||
|         if (std::get<1>(*tdata)->m_stopcr.stop_condition()) | ||||
|             nlopt_force_stop(std::get<2>(*tdata)); | ||||
| 
 | ||||
|         auto fnptr  = std::get<0>(*tdata); | ||||
|         auto funval = to_arr<N>(params); | ||||
| 
 | ||||
|         double scoreval = 0.; | ||||
|         using RetT = decltype((*fnptr)(funval)); | ||||
|         if constexpr (std::is_convertible_v<RetT, ScoreGradient<N>>) { | ||||
|             ScoreGradient<N> score = (*fnptr)(funval); | ||||
|             for (size_t i = 0; i < n; ++i) gradient[i] = (*score.gradient)[i]; | ||||
|             scoreval = score.score; | ||||
|         } else { | ||||
|             scoreval = (*fnptr)(funval); | ||||
|         } | ||||
| 
 | ||||
|         return scoreval; | ||||
|     } | ||||
| 
 | ||||
|     template<size_t N> | ||||
|     void set_up(NLopt &nl, const Bounds<N>& bounds) | ||||
|     { | ||||
|         std::array<double, N> lb, ub; | ||||
| 
 | ||||
|         for (size_t i = 0; i < N; ++i) { | ||||
|             lb[i] = bounds[i].min(); | ||||
|             ub[i] = bounds[i].max(); | ||||
|         } | ||||
| 
 | ||||
|         nlopt_set_lower_bounds(nl.ptr, lb.data()); | ||||
|         nlopt_set_upper_bounds(nl.ptr, ub.data()); | ||||
| 
 | ||||
|         double abs_diff = m_stopcr.abs_score_diff(); | ||||
|         double rel_diff = m_stopcr.rel_score_diff(); | ||||
|         double stopval = m_stopcr.stop_score(); | ||||
|         if(!std::isnan(abs_diff)) nlopt_set_ftol_abs(nl.ptr, abs_diff); | ||||
|         if(!std::isnan(rel_diff)) nlopt_set_ftol_rel(nl.ptr, rel_diff); | ||||
|         if(!std::isnan(stopval))  nlopt_set_stopval(nl.ptr, stopval); | ||||
| 
 | ||||
|         if(this->m_stopcr.max_iterations() > 0) | ||||
|             nlopt_set_maxeval(nl.ptr, this->m_stopcr.max_iterations()); | ||||
|     } | ||||
| 
 | ||||
|     template<class Fn, size_t N> | ||||
|     Result<N> optimize(NLopt &nl, Fn &&fn, const Input<N> &initvals) | ||||
|     { | ||||
|         Result<N> r; | ||||
| 
 | ||||
|         TOptData<Fn> data = std::make_tuple(&fn, this, nl.ptr); | ||||
| 
 | ||||
|         switch(m_dir) { | ||||
|         case OptDir::MIN: | ||||
|             nlopt_set_min_objective(nl.ptr, optfunc<Fn, N>, &data); break; | ||||
|         case OptDir::MAX: | ||||
|             nlopt_set_max_objective(nl.ptr, optfunc<Fn, N>, &data); break; | ||||
|         } | ||||
| 
 | ||||
|         r.optimum = initvals; | ||||
|         r.resultcode = nlopt_optimize(nl.ptr, r.optimum.data(), &r.score); | ||||
| 
 | ||||
|         return r; | ||||
|     } | ||||
| 
 | ||||
| public: | ||||
| 
 | ||||
|     template<class Func, size_t N> | ||||
|     Result<N> optimize(Func&& func, | ||||
|                        const Input<N> &initvals, | ||||
|                        const Bounds<N>& bounds) | ||||
|     { | ||||
|         NLopt nl{alg, N}; | ||||
|         set_up(nl, bounds); | ||||
| 
 | ||||
|         return optimize(nl, std::forward<Func>(func), initvals); | ||||
|     } | ||||
| 
 | ||||
|     explicit NLoptOpt(StopCriteria stopcr = {}) : m_stopcr(stopcr) {} | ||||
| 
 | ||||
|     void set_criteria(const StopCriteria &cr) { m_stopcr = cr; } | ||||
|     const StopCriteria &get_criteria() const noexcept { return m_stopcr; } | ||||
|     void set_dir(OptDir dir) noexcept { m_dir = dir; } | ||||
| 
 | ||||
|     void seed(long s) { nlopt_srand(s); } | ||||
| }; | ||||
| 
 | ||||
| template<nlopt_algorithm glob, nlopt_algorithm loc> | ||||
| class NLoptOpt<NLoptAlgComb<glob, loc>>: public NLoptOpt<NLoptAlg<glob>> | ||||
| { | ||||
|     using Base = NLoptOpt<NLoptAlg<glob>>; | ||||
| public: | ||||
| 
 | ||||
|     template<class Fn, size_t N> | ||||
|     Result<N> optimize(Fn&& f, | ||||
|                        const Input<N> &initvals, | ||||
|                        const Bounds<N>& bounds) | ||||
|     { | ||||
|         NLopt nl_glob{glob, N}, nl_loc{loc, N}; | ||||
| 
 | ||||
|         Base::set_up(nl_glob, bounds); | ||||
|         Base::set_up(nl_loc, bounds); | ||||
|         nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr); | ||||
| 
 | ||||
|         return Base::optimize(nl_glob, std::forward<Fn>(f), initvals); | ||||
|     } | ||||
| 
 | ||||
|     explicit NLoptOpt(StopCriteria stopcr = {}) : Base{stopcr} {} | ||||
| }; | ||||
| 
 | ||||
| } // namespace detail;
 | ||||
| 
 | ||||
| // Optimizers based on NLopt.
 | ||||
| template<class M> class Optimizer<M, detail::NLoptOnly<M>> { | ||||
|     detail::NLoptOpt<M> m_opt; | ||||
| 
 | ||||
| public: | ||||
| 
 | ||||
|     Optimizer& to_max() { m_opt.set_dir(detail::OptDir::MAX); return *this; } | ||||
|     Optimizer& to_min() { m_opt.set_dir(detail::OptDir::MIN); return *this; } | ||||
| 
 | ||||
|     template<class Func, size_t N> | ||||
|     Result<N> optimize(Func&& func, | ||||
|                        const Input<N> &initvals, | ||||
|                        const Bounds<N>& bounds) | ||||
|     { | ||||
|         return m_opt.optimize(std::forward<Func>(func), initvals, bounds); | ||||
|     } | ||||
| 
 | ||||
|     explicit Optimizer(StopCriteria stopcr = {}) : m_opt(stopcr) {} | ||||
| 
 | ||||
|     Optimizer &set_criteria(const StopCriteria &cr) | ||||
|     { | ||||
|         m_opt.set_criteria(cr); return *this; | ||||
|     } | ||||
| 
 | ||||
|     const StopCriteria &get_criteria() const { return m_opt.get_criteria(); } | ||||
| 
 | ||||
|     void seed(long s) { m_opt.seed(s); } | ||||
| }; | ||||
| 
 | ||||
| template<size_t N> Bounds<N> bounds(const Bound (&b) [N]) { return detail::to_arr(b); } | ||||
| template<size_t N> Input<N> initvals(const double (&a) [N]) { return detail::to_arr(a); } | ||||
| template<size_t N> auto score_gradient(double s, const double (&grad)[N]) | ||||
| { | ||||
|     return ScoreGradient<N>(s, detail::to_arr(grad)); | ||||
| } | ||||
| 
 | ||||
| // Predefinded NLopt algorithms that are used in the codebase
 | ||||
| using AlgNLoptGenetic = detail::NLoptAlgComb<NLOPT_GN_ESCH>; | ||||
| using AlgNLoptSubplex = detail::NLoptAlg<NLOPT_LN_SBPLX>; | ||||
| using AlgNLoptSimplex = detail::NLoptAlg<NLOPT_LN_NELDERMEAD>; | ||||
| 
 | ||||
| // TODO: define others if needed...
 | ||||
| 
 | ||||
| // Helper defs for pre-crafted global and local optimizers that work well.
 | ||||
| using DefaultGlobalOptimizer = Optimizer<AlgNLoptGenetic>; | ||||
| using DefaultLocalOptimizer  = Optimizer<AlgNLoptSubplex>; | ||||
| 
 | ||||
| }} // namespace Slic3r::opt
 | ||||
| 
 | ||||
| #endif // NLOPTOPTIMIZER_HPP
 | ||||
|  | @ -60,10 +60,13 @@ inline int64_t cross2(const Vec2i64 &v1, const Vec2i64 &v2) { return v1(0) * v2( | |||
| inline float   cross2(const Vec2f   &v1, const Vec2f   &v2) { return v1(0) * v2(1) - v1(1) * v2(0); } | ||||
| inline double  cross2(const Vec2d   &v1, const Vec2d   &v2) { return v1(0) * v2(1) - v1(1) * v2(0); } | ||||
| 
 | ||||
| inline Vec2i32 to_2d(const Vec2i32 &pt3) { return Vec2i32(pt3(0), pt3(1)); } | ||||
| inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); } | ||||
| inline Vec2f   to_2d(const Vec3f   &pt3) { return Vec2f  (pt3(0), pt3(1)); } | ||||
| inline Vec2d   to_2d(const Vec3d   &pt3) { return Vec2d  (pt3(0), pt3(1)); } | ||||
| template<class T, int N> Eigen::Matrix<T,  2, 1, Eigen::DontAlign> | ||||
| to_2d(const Eigen::Matrix<T,  N, 1, Eigen::DontAlign> &ptN) { return {ptN(0), ptN(1)}; } | ||||
| 
 | ||||
| //inline Vec2i32 to_2d(const Vec3i32 &pt3) { return Vec2i32(pt3(0), pt3(1)); }
 | ||||
| //inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); }
 | ||||
| //inline Vec2f   to_2d(const Vec3f   &pt3) { return Vec2f  (pt3(0), pt3(1)); }
 | ||||
| //inline Vec2d   to_2d(const Vec3d   &pt3) { return Vec2d  (pt3(0), pt3(1)); }
 | ||||
| 
 | ||||
| inline Vec3d   to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); } | ||||
| inline Vec3f   to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); } | ||||
|  |  | |||
|  | @ -2715,7 +2715,7 @@ void PrintConfigDef::init_sla_params() | |||
|     def->set_default_value(new ConfigOptionBool(true)); | ||||
| 
 | ||||
|     def = this->add("support_head_front_diameter", coFloat); | ||||
|     def->label = L("Support head front diameter"); | ||||
|     def->label = L("Pinhead front diameter"); | ||||
|     def->category = L("Supports"); | ||||
|     def->tooltip = L("Diameter of the pointing side of the head"); | ||||
|     def->sidetext = L("mm"); | ||||
|  | @ -2724,7 +2724,7 @@ void PrintConfigDef::init_sla_params() | |||
|     def->set_default_value(new ConfigOptionFloat(0.4)); | ||||
| 
 | ||||
|     def = this->add("support_head_penetration", coFloat); | ||||
|     def->label = L("Support head penetration"); | ||||
|     def->label = L("Head penetration"); | ||||
|     def->category = L("Supports"); | ||||
|     def->tooltip = L("How much the pinhead has to penetrate the model surface"); | ||||
|     def->sidetext = L("mm"); | ||||
|  | @ -2733,7 +2733,7 @@ void PrintConfigDef::init_sla_params() | |||
|     def->set_default_value(new ConfigOptionFloat(0.2)); | ||||
| 
 | ||||
|     def = this->add("support_head_width", coFloat); | ||||
|     def->label = L("Support head width"); | ||||
|     def->label = L("Pinhead width"); | ||||
|     def->category = L("Supports"); | ||||
|     def->tooltip = L("Width from the back sphere center to the front sphere center"); | ||||
|     def->sidetext = L("mm"); | ||||
|  | @ -2743,7 +2743,7 @@ void PrintConfigDef::init_sla_params() | |||
|     def->set_default_value(new ConfigOptionFloat(1.0)); | ||||
| 
 | ||||
|     def = this->add("support_pillar_diameter", coFloat); | ||||
|     def->label = L("Support pillar diameter"); | ||||
|     def->label = L("Pillar diameter"); | ||||
|     def->category = L("Supports"); | ||||
|     def->tooltip = L("Diameter in mm of the support pillars"); | ||||
|     def->sidetext = L("mm"); | ||||
|  | @ -2751,6 +2751,17 @@ void PrintConfigDef::init_sla_params() | |||
|     def->max = 15; | ||||
|     def->mode = comSimple; | ||||
|     def->set_default_value(new ConfigOptionFloat(1.0)); | ||||
| 
 | ||||
|     def = this->add("support_small_pillar_diameter_percent", coPercent); | ||||
|     def->label = L("Small pillar diameter percent"); | ||||
|     def->category = L("Supports"); | ||||
|     def->tooltip = L("The percentage of smaller pillars compared to the normal pillar diameter " | ||||
|                      "which are used in problematic areas where a normal pilla cannot fit."); | ||||
|     def->sidetext = L("%"); | ||||
|     def->min = 1; | ||||
|     def->max = 100; | ||||
|     def->mode = comExpert; | ||||
|     def->set_default_value(new ConfigOptionPercent(50)); | ||||
|      | ||||
|     def = this->add("support_max_bridges_on_pillar", coInt); | ||||
|     def->label = L("Max bridges on a pillar"); | ||||
|  | @ -2763,7 +2774,7 @@ void PrintConfigDef::init_sla_params() | |||
|     def->set_default_value(new ConfigOptionInt(3)); | ||||
| 
 | ||||
|     def = this->add("support_pillar_connection_mode", coEnum); | ||||
|     def->label = L("Support pillar connection mode"); | ||||
|     def->label = L("Pillar connection mode"); | ||||
|     def->tooltip = L("Controls the bridge type between two neighboring pillars." | ||||
|                      " Can be zig-zag, cross (double zig-zag) or dynamic which" | ||||
|                      " will automatically switch between the first two depending" | ||||
|  |  | |||
|  | @ -1018,6 +1018,10 @@ public: | |||
| 
 | ||||
|     // Radius in mm of the support pillars.
 | ||||
|     ConfigOptionFloat support_pillar_diameter /*= 0.8*/; | ||||
| 
 | ||||
|     // The percentage of smaller pillars compared to the normal pillar diameter
 | ||||
|     // which are used in problematic areas where a normal pilla cannot fit.
 | ||||
|     ConfigOptionPercent support_small_pillar_diameter_percent; | ||||
|      | ||||
|     // How much bridge (supporting another pinhead) can be placed on a pillar.
 | ||||
|     ConfigOptionInt   support_max_bridges_on_pillar; | ||||
|  | @ -1142,6 +1146,7 @@ protected: | |||
|         OPT_PTR(support_head_penetration); | ||||
|         OPT_PTR(support_head_width); | ||||
|         OPT_PTR(support_pillar_diameter); | ||||
|         OPT_PTR(support_small_pillar_diameter_percent); | ||||
|         OPT_PTR(support_max_bridges_on_pillar); | ||||
|         OPT_PTR(support_pillar_connection_mode); | ||||
|         OPT_PTR(support_buildplate_only); | ||||
|  |  | |||
|  | @ -1,7 +1,9 @@ | |||
| #ifndef SLA_BOOSTADAPTER_HPP | ||||
| #define SLA_BOOSTADAPTER_HPP | ||||
| 
 | ||||
| #include <libslic3r/SLA/Common.hpp> | ||||
| #include <libslic3r/Point.hpp> | ||||
| #include <libslic3r/BoundingBox.hpp> | ||||
| 
 | ||||
| #include <boost/geometry.hpp> | ||||
| 
 | ||||
| namespace boost { | ||||
|  |  | |||
							
								
								
									
										152
									
								
								src/libslic3r/SLA/Clustering.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								src/libslic3r/SLA/Clustering.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,152 @@ | |||
| #include "Clustering.hpp" | ||||
| #include "boost/geometry/index/rtree.hpp" | ||||
| 
 | ||||
| #include <libslic3r/SLA/SpatIndex.hpp> | ||||
| #include <libslic3r/SLA/BoostAdapter.hpp> | ||||
| 
 | ||||
| namespace Slic3r { namespace sla { | ||||
| 
 | ||||
| namespace bgi = boost::geometry::index; | ||||
| using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >; | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2) | ||||
| { | ||||
|     return e1.second < e2.second; | ||||
| }; | ||||
| 
 | ||||
| ClusteredPoints cluster(Index3D &sindex, | ||||
|                         unsigned max_points, | ||||
|                         std::function<std::vector<PointIndexEl>( | ||||
|                             const Index3D &, const PointIndexEl &)> qfn) | ||||
| { | ||||
|     using Elems = std::vector<PointIndexEl>; | ||||
| 
 | ||||
|     // Recursive function for visiting all the points in a given distance to
 | ||||
|     // each other
 | ||||
|     std::function<void(Elems&, Elems&)> group = | ||||
|         [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster) | ||||
|     { | ||||
|         for(auto& p : pts) { | ||||
|             std::vector<PointIndexEl> tmp = qfn(sindex, p); | ||||
| 
 | ||||
|             std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements); | ||||
| 
 | ||||
|             Elems newpts; | ||||
|             std::set_difference(tmp.begin(), tmp.end(), | ||||
|                                 cluster.begin(), cluster.end(), | ||||
|                                 std::back_inserter(newpts), cmp_ptidx_elements); | ||||
| 
 | ||||
|             int c = max_points && newpts.size() + cluster.size() > max_points? | ||||
|                         int(max_points - cluster.size()) : int(newpts.size()); | ||||
| 
 | ||||
|             cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c); | ||||
|             std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements); | ||||
| 
 | ||||
|             if(!newpts.empty() && (!max_points || cluster.size() < max_points)) | ||||
|                 group(newpts, cluster); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     std::vector<Elems> clusters; | ||||
|     for(auto it = sindex.begin(); it != sindex.end();) { | ||||
|         Elems cluster = {}; | ||||
|         Elems pts = {*it}; | ||||
|         group(pts, cluster); | ||||
| 
 | ||||
|         for(auto& c : cluster) sindex.remove(c); | ||||
|         it = sindex.begin(); | ||||
| 
 | ||||
|         clusters.emplace_back(cluster); | ||||
|     } | ||||
| 
 | ||||
|     ClusteredPoints result; | ||||
|     for(auto& cluster : clusters) { | ||||
|         result.emplace_back(); | ||||
|         for(auto c : cluster) result.back().emplace_back(c.second); | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex, | ||||
|                                            const PointIndexEl& p, | ||||
|                                            double dist, | ||||
|                                            unsigned max_points) | ||||
| { | ||||
|     std::vector<PointIndexEl> tmp; tmp.reserve(max_points); | ||||
|     sindex.query( | ||||
|         bgi::nearest(p.first, max_points), | ||||
|         std::back_inserter(tmp) | ||||
|         ); | ||||
| 
 | ||||
|     for(auto it = tmp.begin(); it < tmp.end(); ++it) | ||||
|         if((p.first - it->first).norm() > dist) it = tmp.erase(it); | ||||
| 
 | ||||
|     return tmp; | ||||
| } | ||||
| 
 | ||||
| } // namespace
 | ||||
| 
 | ||||
| // Clustering a set of points by the given criteria
 | ||||
| ClusteredPoints cluster( | ||||
|     const std::vector<unsigned>& indices, | ||||
|     std::function<Vec3d(unsigned)> pointfn, | ||||
|     double dist, | ||||
|     unsigned max_points) | ||||
| { | ||||
|     // A spatial index for querying the nearest points
 | ||||
|     Index3D sindex; | ||||
| 
 | ||||
|     // Build the index
 | ||||
|     for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); | ||||
| 
 | ||||
|     return cluster(sindex, max_points, | ||||
|                    [dist, max_points](const Index3D& sidx, const PointIndexEl& p) | ||||
|                    { | ||||
|                        return distance_queryfn(sidx, p, dist, max_points); | ||||
|                    }); | ||||
| } | ||||
| 
 | ||||
| // Clustering a set of points by the given criteria
 | ||||
| ClusteredPoints cluster( | ||||
|     const std::vector<unsigned>& indices, | ||||
|     std::function<Vec3d(unsigned)> pointfn, | ||||
|     std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate, | ||||
|     unsigned max_points) | ||||
| { | ||||
|     // A spatial index for querying the nearest points
 | ||||
|     Index3D sindex; | ||||
| 
 | ||||
|     // Build the index
 | ||||
|     for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); | ||||
| 
 | ||||
|     return cluster(sindex, max_points, | ||||
|                    [max_points, predicate](const Index3D& sidx, const PointIndexEl& p) | ||||
|                    { | ||||
|                        std::vector<PointIndexEl> tmp; tmp.reserve(max_points); | ||||
|                        sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){ | ||||
|                                       return predicate(p, e); | ||||
|                                   }), std::back_inserter(tmp)); | ||||
|                        return tmp; | ||||
|                    }); | ||||
| } | ||||
| 
 | ||||
| ClusteredPoints cluster(const Eigen::MatrixXd& pts, double dist, unsigned max_points) | ||||
| { | ||||
|     // A spatial index for querying the nearest points
 | ||||
|     Index3D sindex; | ||||
| 
 | ||||
|     // Build the index
 | ||||
|     for(Eigen::Index i = 0; i < pts.rows(); i++) | ||||
|         sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i))); | ||||
| 
 | ||||
|     return cluster(sindex, max_points, | ||||
|                    [dist, max_points](const Index3D& sidx, const PointIndexEl& p) | ||||
|                    { | ||||
|                        return distance_queryfn(sidx, p, dist, max_points); | ||||
|                    }); | ||||
| } | ||||
| 
 | ||||
| }} // namespace Slic3r::sla
 | ||||
|  | @ -2,7 +2,8 @@ | |||
| #define SLA_CLUSTERING_HPP | ||||
| 
 | ||||
| #include <vector> | ||||
| #include <libslic3r/SLA/Common.hpp> | ||||
| 
 | ||||
| #include <libslic3r/Point.hpp> | ||||
| #include <libslic3r/SLA/SpatIndex.hpp> | ||||
| 
 | ||||
| namespace Slic3r { namespace sla { | ||||
|  | @ -16,7 +17,7 @@ ClusteredPoints cluster(const std::vector<unsigned>& indices, | |||
|                         double dist, | ||||
|                         unsigned max_points); | ||||
| 
 | ||||
| ClusteredPoints cluster(const PointSet& points, | ||||
| ClusteredPoints cluster(const Eigen::MatrixXd& points, | ||||
|                         double dist, | ||||
|                         unsigned max_points); | ||||
| 
 | ||||
|  | @ -26,5 +27,56 @@ ClusteredPoints cluster( | |||
|     std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate, | ||||
|     unsigned max_points); | ||||
| 
 | ||||
| }} | ||||
| // This function returns the position of the centroid in the input 'clust'
 | ||||
| // vector of point indices.
 | ||||
| template<class DistFn, class PointFn> | ||||
| long cluster_centroid(const ClusterEl &clust, PointFn pointfn, DistFn df) | ||||
| { | ||||
|     switch(clust.size()) { | ||||
|     case 0: /* empty cluster */ return -1; | ||||
|     case 1: /* only one element */ return 0; | ||||
|     case 2: /* if two elements, there is no center */ return 0; | ||||
|     default: ; | ||||
|     } | ||||
| 
 | ||||
|     // The function works by calculating for each point the average distance
 | ||||
|     // from all the other points in the cluster. We create a selector bitmask of
 | ||||
|     // the same size as the cluster. The bitmask will have two true bits and
 | ||||
|     // false bits for the rest of items and we will loop through all the
 | ||||
|     // permutations of the bitmask (combinations of two points). Get the
 | ||||
|     // distance for the two points and add the distance to the averages.
 | ||||
|     // The point with the smallest average than wins.
 | ||||
| 
 | ||||
|     // The complexity should be O(n^2) but we will mostly apply this function
 | ||||
|     // for small clusters only (cca 3 elements)
 | ||||
| 
 | ||||
|     std::vector<bool> sel(clust.size(), false);   // create full zero bitmask
 | ||||
|     std::fill(sel.end() - 2, sel.end(), true);    // insert the two ones
 | ||||
|     std::vector<double> avgs(clust.size(), 0.0);  // store the average distances
 | ||||
| 
 | ||||
|     do { | ||||
|         std::array<size_t, 2> idx; | ||||
|         for(size_t i = 0, j = 0; i < clust.size(); i++) | ||||
|             if(sel[i]) idx[j++] = i; | ||||
| 
 | ||||
|         double d = df(pointfn(clust[idx[0]]), | ||||
|                       pointfn(clust[idx[1]])); | ||||
| 
 | ||||
|         // add the distance to the sums for both associated points
 | ||||
|         for(auto i : idx) avgs[i] += d; | ||||
| 
 | ||||
|         // now continue with the next permutation of the bitmask with two 1s
 | ||||
|     } while(std::next_permutation(sel.begin(), sel.end())); | ||||
| 
 | ||||
|     // Divide by point size in the cluster to get the average (may be redundant)
 | ||||
|     for(auto& a : avgs) a /= clust.size(); | ||||
| 
 | ||||
|     // get the lowest average distance and return the index
 | ||||
|     auto minit = std::min_element(avgs.begin(), avgs.end()); | ||||
|     return long(minit - avgs.begin()); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| }} // namespace Slic3r::sla
 | ||||
| 
 | ||||
| #endif // CLUSTERING_HPP
 | ||||
|  |  | |||
|  | @ -1,27 +0,0 @@ | |||
| #ifndef SLA_COMMON_HPP | ||||
| #define SLA_COMMON_HPP | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| #include <numeric> | ||||
| #include <functional> | ||||
| #include <Eigen/Geometry> | ||||
| 
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
|      | ||||
| // Typedefs from Point.hpp
 | ||||
| typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> Vec3f; | ||||
| typedef Eigen::Matrix<double, 3, 1, Eigen::DontAlign> Vec3d; | ||||
| typedef Eigen::Matrix<int, 3, 1, Eigen::DontAlign> Vec3i; | ||||
| typedef Eigen::Matrix<int, 4, 1, Eigen::DontAlign> Vec4i; | ||||
| 
 | ||||
| namespace sla { | ||||
| 
 | ||||
| using PointSet = Eigen::MatrixXd; | ||||
| 
 | ||||
| } // namespace sla
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
| 
 | ||||
| #endif // SLASUPPORTTREE_HPP
 | ||||
|  | @ -1,5 +1,5 @@ | |||
| #include <libslic3r/SLA/Contour3D.hpp> | ||||
| #include <libslic3r/SLA/EigenMesh3D.hpp> | ||||
| #include <libslic3r/SLA/IndexedMesh.hpp> | ||||
| 
 | ||||
| #include <libslic3r/Format/objparser.hpp> | ||||
| 
 | ||||
|  | @ -27,7 +27,7 @@ Contour3D::Contour3D(TriangleMesh &&trmesh) | |||
|     faces3.swap(trmesh.its.indices); | ||||
| } | ||||
| 
 | ||||
| Contour3D::Contour3D(const EigenMesh3D &emesh) { | ||||
| Contour3D::Contour3D(const IndexedMesh &emesh) { | ||||
|     points.reserve(emesh.vertices().size()); | ||||
|     faces3.reserve(emesh.indices().size()); | ||||
|      | ||||
|  |  | |||
|  | @ -1,13 +1,16 @@ | |||
| #ifndef SLA_CONTOUR3D_HPP | ||||
| #define SLA_CONTOUR3D_HPP | ||||
| 
 | ||||
| #include <libslic3r/SLA/Common.hpp> | ||||
| 
 | ||||
| #include <libslic3r/TriangleMesh.hpp> | ||||
| 
 | ||||
| namespace Slic3r { namespace sla { | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| class EigenMesh3D; | ||||
| // Used for quads (TODO: remove this, and convert quads to triangles in OpenVDBUtils)
 | ||||
| using Vec4i = Eigen::Matrix<int, 4, 1, Eigen::DontAlign>; | ||||
| 
 | ||||
| namespace sla { | ||||
| 
 | ||||
| class IndexedMesh; | ||||
| 
 | ||||
| /// Dumb vertex mesh consisting of triangles (or) quads. Capable of merging with
 | ||||
| /// other meshes of this type and converting to and from other mesh formats.
 | ||||
|  | @ -19,7 +22,7 @@ struct Contour3D { | |||
|     Contour3D() = default; | ||||
|     Contour3D(const TriangleMesh &trmesh); | ||||
|     Contour3D(TriangleMesh &&trmesh); | ||||
|     Contour3D(const EigenMesh3D  &emesh); | ||||
|     Contour3D(const IndexedMesh  &emesh); | ||||
|      | ||||
|     Contour3D& merge(const Contour3D& ctr); | ||||
|     Contour3D& merge(const Pointf3s& triangles); | ||||
|  |  | |||
|  | @ -3,11 +3,10 @@ | |||
| #include <libslic3r/OpenVDBUtils.hpp> | ||||
| #include <libslic3r/TriangleMesh.hpp> | ||||
| #include <libslic3r/SLA/Hollowing.hpp> | ||||
| #include <libslic3r/SLA/Contour3D.hpp> | ||||
| #include <libslic3r/SLA/EigenMesh3D.hpp> | ||||
| #include <libslic3r/SLA/SupportTreeBuilder.hpp> | ||||
| #include <libslic3r/SLA/IndexedMesh.hpp> | ||||
| #include <libslic3r/ClipperUtils.hpp> | ||||
| #include <libslic3r/SimplifyMesh.hpp> | ||||
| #include <libslic3r/SLA/SupportTreeMesher.hpp> | ||||
| 
 | ||||
| #include <boost/log/trivial.hpp> | ||||
| 
 | ||||
|  | @ -160,7 +159,7 @@ bool DrainHole::get_intersections(const Vec3f& s, const Vec3f& dir, | |||
|     const Eigen::ParametrizedLine<float, 3> ray(s, dir.normalized()); | ||||
| 
 | ||||
|     for (size_t i=0; i<2; ++i) | ||||
|         out[i] = std::make_pair(sla::EigenMesh3D::hit_result::infty(), Vec3d::Zero()); | ||||
|         out[i] = std::make_pair(sla::IndexedMesh::hit_result::infty(), Vec3d::Zero()); | ||||
| 
 | ||||
|     const float sqr_radius = pow(radius, 2.f); | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,7 +2,6 @@ | |||
| #define SLA_HOLLOWING_HPP | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <libslic3r/SLA/Common.hpp> | ||||
| #include <libslic3r/SLA/Contour3D.hpp> | ||||
| #include <libslic3r/SLA/JobController.hpp> | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,187 +1,18 @@ | |||
| #include <cmath> | ||||
| #include <libslic3r/SLA/Common.hpp> | ||||
| #include <libslic3r/SLA/Concurrency.hpp> | ||||
| #include <libslic3r/SLA/SpatIndex.hpp> | ||||
| #include <libslic3r/SLA/EigenMesh3D.hpp> | ||||
| #include <libslic3r/SLA/Contour3D.hpp> | ||||
| #include <libslic3r/SLA/Clustering.hpp> | ||||
| #include "IndexedMesh.hpp" | ||||
| #include "Concurrency.hpp" | ||||
| 
 | ||||
| #include <libslic3r/AABBTreeIndirect.hpp> | ||||
| #include <libslic3r/TriangleMesh.hpp> | ||||
| 
 | ||||
| // for concave hull merging decisions
 | ||||
| #include <libslic3r/SLA/BoostAdapter.hpp> | ||||
| #include "boost/geometry/index/rtree.hpp" | ||||
| 
 | ||||
| #ifdef _MSC_VER | ||||
| #pragma warning(push) | ||||
| #pragma warning(disable: 4244) | ||||
| #pragma warning(disable: 4267) | ||||
| #endif | ||||
| 
 | ||||
| 
 | ||||
| #include <igl/remove_duplicate_vertices.h> | ||||
| #include <numeric> | ||||
| 
 | ||||
| #ifdef SLIC3R_HOLE_RAYCASTER | ||||
|   #include <libslic3r/SLA/Hollowing.hpp> | ||||
| #include <libslic3r/SLA/Hollowing.hpp> | ||||
| #endif | ||||
| 
 | ||||
| namespace Slic3r { namespace sla { | ||||
| 
 | ||||
| #ifdef _MSC_VER | ||||
| #pragma warning(pop) | ||||
| #endif | ||||
| 
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace sla { | ||||
| 
 | ||||
| 
 | ||||
| /* **************************************************************************
 | ||||
|  * PointIndex implementation | ||||
|  * ************************************************************************** */ | ||||
| 
 | ||||
| class PointIndex::Impl { | ||||
| public: | ||||
|     using BoostIndex = boost::geometry::index::rtree< PointIndexEl, | ||||
|                                                      boost::geometry::index::rstar<16, 4> /* ? */ >; | ||||
|      | ||||
|     BoostIndex m_store; | ||||
| }; | ||||
| 
 | ||||
| PointIndex::PointIndex(): m_impl(new Impl()) {} | ||||
| PointIndex::~PointIndex() {} | ||||
| 
 | ||||
| PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} | ||||
| PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} | ||||
| 
 | ||||
| PointIndex& PointIndex::operator=(const PointIndex &cpy) | ||||
| { | ||||
|     m_impl.reset(new Impl(*cpy.m_impl)); | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| PointIndex& PointIndex::operator=(PointIndex &&cpy) | ||||
| { | ||||
|     m_impl.swap(cpy.m_impl); | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| void PointIndex::insert(const PointIndexEl &el) | ||||
| { | ||||
|     m_impl->m_store.insert(el); | ||||
| } | ||||
| 
 | ||||
| bool PointIndex::remove(const PointIndexEl& el) | ||||
| { | ||||
|     return m_impl->m_store.remove(el) == 1; | ||||
| } | ||||
| 
 | ||||
| std::vector<PointIndexEl> | ||||
| PointIndex::query(std::function<bool(const PointIndexEl &)> fn) const | ||||
| { | ||||
|     namespace bgi = boost::geometry::index; | ||||
|      | ||||
|     std::vector<PointIndexEl> ret; | ||||
|     m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret)); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) const | ||||
| { | ||||
|     namespace bgi = boost::geometry::index; | ||||
|     std::vector<PointIndexEl> ret; ret.reserve(k); | ||||
|     m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret)); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| size_t PointIndex::size() const | ||||
| { | ||||
|     return m_impl->m_store.size(); | ||||
| } | ||||
| 
 | ||||
| void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) | ||||
| { | ||||
|     for(auto& el : m_impl->m_store) fn(el); | ||||
| } | ||||
| 
 | ||||
| void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) const | ||||
| { | ||||
|     for(const auto &el : m_impl->m_store) fn(el); | ||||
| } | ||||
| 
 | ||||
| /* **************************************************************************
 | ||||
|  * BoxIndex implementation | ||||
|  * ************************************************************************** */ | ||||
| 
 | ||||
| class BoxIndex::Impl { | ||||
| public: | ||||
|     using BoostIndex = boost::geometry::index:: | ||||
|         rtree<BoxIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>; | ||||
|      | ||||
|     BoostIndex m_store; | ||||
| }; | ||||
| 
 | ||||
| BoxIndex::BoxIndex(): m_impl(new Impl()) {} | ||||
| BoxIndex::~BoxIndex() {} | ||||
| 
 | ||||
| BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} | ||||
| BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} | ||||
| 
 | ||||
| BoxIndex& BoxIndex::operator=(const BoxIndex &cpy) | ||||
| { | ||||
|     m_impl.reset(new Impl(*cpy.m_impl)); | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| BoxIndex& BoxIndex::operator=(BoxIndex &&cpy) | ||||
| { | ||||
|     m_impl.swap(cpy.m_impl); | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| void BoxIndex::insert(const BoxIndexEl &el) | ||||
| { | ||||
|     m_impl->m_store.insert(el); | ||||
| } | ||||
| 
 | ||||
| bool BoxIndex::remove(const BoxIndexEl& el) | ||||
| { | ||||
|     return m_impl->m_store.remove(el) == 1; | ||||
| } | ||||
| 
 | ||||
| std::vector<BoxIndexEl> BoxIndex::query(const BoundingBox &qrbb, | ||||
|                                         BoxIndex::QueryType qt) | ||||
| { | ||||
|     namespace bgi = boost::geometry::index; | ||||
|      | ||||
|     std::vector<BoxIndexEl> ret; ret.reserve(m_impl->m_store.size()); | ||||
|      | ||||
|     switch (qt) { | ||||
|     case qtIntersects: | ||||
|         m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret)); | ||||
|         break; | ||||
|     case qtWithin: | ||||
|         m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret)); | ||||
|     } | ||||
|      | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| size_t BoxIndex::size() const | ||||
| { | ||||
|     return m_impl->m_store.size(); | ||||
| } | ||||
| 
 | ||||
| void BoxIndex::foreach(std::function<void (const BoxIndexEl &)> fn) | ||||
| { | ||||
|     for(auto& el : m_impl->m_store) fn(el); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* ****************************************************************************
 | ||||
|  * EigenMesh3D implementation | ||||
|  * ****************************************************************************/ | ||||
| 
 | ||||
| 
 | ||||
| class EigenMesh3D::AABBImpl { | ||||
| class IndexedMesh::AABBImpl { | ||||
| private: | ||||
|     AABBTreeIndirect::Tree3f m_tree; | ||||
| 
 | ||||
|  | @ -189,7 +20,7 @@ public: | |||
|     void init(const TriangleMesh& tm) | ||||
|     { | ||||
|         m_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( | ||||
|                     tm.its.vertices, tm.its.indices); | ||||
|             tm.its.vertices, tm.its.indices); | ||||
|     } | ||||
| 
 | ||||
|     void intersect_ray(const TriangleMesh& tm, | ||||
|  | @ -215,9 +46,9 @@ public: | |||
|         size_t idx_unsigned = 0; | ||||
|         Vec3d closest_vec3d(closest); | ||||
|         double dist = AABBTreeIndirect::squared_distance_to_indexed_triangle_set( | ||||
|                           tm.its.vertices, | ||||
|                           tm.its.indices, | ||||
|                           m_tree, point, idx_unsigned, closest_vec3d); | ||||
|             tm.its.vertices, | ||||
|             tm.its.indices, | ||||
|             m_tree, point, idx_unsigned, closest_vec3d); | ||||
|         i = int(idx_unsigned); | ||||
|         closest = closest_vec3d; | ||||
|         return dist; | ||||
|  | @ -226,72 +57,71 @@ public: | |||
| 
 | ||||
| static const constexpr double MESH_EPS = 1e-6; | ||||
| 
 | ||||
| EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh) | ||||
| IndexedMesh::IndexedMesh(const TriangleMesh& tmesh) | ||||
|     : m_aabb(new AABBImpl()), m_tm(&tmesh) | ||||
| { | ||||
|     auto&& bb = tmesh.bounding_box(); | ||||
|     m_ground_level += bb.min(Z); | ||||
|      | ||||
| 
 | ||||
|     // Build the AABB accelaration tree
 | ||||
|     m_aabb->init(tmesh); | ||||
| } | ||||
| 
 | ||||
| EigenMesh3D::~EigenMesh3D() {} | ||||
| IndexedMesh::~IndexedMesh() {} | ||||
| 
 | ||||
| EigenMesh3D::EigenMesh3D(const EigenMesh3D &other): | ||||
| IndexedMesh::IndexedMesh(const IndexedMesh &other): | ||||
|     m_tm(other.m_tm), m_ground_level(other.m_ground_level), | ||||
|     m_aabb( new AABBImpl(*other.m_aabb) ) {} | ||||
| 
 | ||||
| 
 | ||||
| EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other) | ||||
| IndexedMesh &IndexedMesh::operator=(const IndexedMesh &other) | ||||
| { | ||||
|     m_tm = other.m_tm; | ||||
|     m_ground_level = other.m_ground_level; | ||||
|     m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this; | ||||
| } | ||||
| 
 | ||||
| EigenMesh3D &EigenMesh3D::operator=(EigenMesh3D &&other) = default; | ||||
| IndexedMesh &IndexedMesh::operator=(IndexedMesh &&other) = default; | ||||
| 
 | ||||
| EigenMesh3D::EigenMesh3D(EigenMesh3D &&other) = default; | ||||
| IndexedMesh::IndexedMesh(IndexedMesh &&other) = default; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| const std::vector<Vec3f>& EigenMesh3D::vertices() const | ||||
| const std::vector<Vec3f>& IndexedMesh::vertices() const | ||||
| { | ||||
|     return m_tm->its.vertices; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| const std::vector<Vec3i>& EigenMesh3D::indices()  const | ||||
| const std::vector<Vec3i>& IndexedMesh::indices()  const | ||||
| { | ||||
|     return m_tm->its.indices; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| const Vec3f& EigenMesh3D::vertices(size_t idx) const | ||||
| const Vec3f& IndexedMesh::vertices(size_t idx) const | ||||
| { | ||||
|     return m_tm->its.vertices[idx]; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| const Vec3i& EigenMesh3D::indices(size_t idx) const | ||||
| const Vec3i& IndexedMesh::indices(size_t idx) const | ||||
| { | ||||
|     return m_tm->its.indices[idx]; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| Vec3d EigenMesh3D::normal_by_face_id(int face_id) const { | ||||
| Vec3d IndexedMesh::normal_by_face_id(int face_id) const { | ||||
|     return m_tm->stl.facet_start[face_id].normal.cast<double>(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| EigenMesh3D::hit_result | ||||
| EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const | ||||
| IndexedMesh::hit_result | ||||
| IndexedMesh::query_ray_hit(const Vec3d &s, const Vec3d &dir) const | ||||
| { | ||||
|     assert(is_approx(dir.norm(), 1.)); | ||||
|     igl::Hit hit; | ||||
|  | @ -319,13 +149,13 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const | |||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| std::vector<EigenMesh3D::hit_result> | ||||
| EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const | ||||
| std::vector<IndexedMesh::hit_result> | ||||
| IndexedMesh::query_ray_hits(const Vec3d &s, const Vec3d &dir) const | ||||
| { | ||||
|     std::vector<EigenMesh3D::hit_result> outs; | ||||
|     std::vector<IndexedMesh::hit_result> outs; | ||||
|     std::vector<igl::Hit> hits; | ||||
|     m_aabb->intersect_ray(*m_tm, s, dir, hits); | ||||
|      | ||||
| 
 | ||||
|     // The sort is necessary, the hits are not always sorted.
 | ||||
|     std::sort(hits.begin(), hits.end(), | ||||
|               [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; }); | ||||
|  | @ -334,13 +164,13 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const | |||
|     // along an axis of a cube due to floating-point approximations in igl (?)
 | ||||
|     hits.erase(std::unique(hits.begin(), hits.end(), | ||||
|                            [](const igl::Hit& a, const igl::Hit& b) | ||||
|                               { return a.t == b.t; }), | ||||
|                            { return a.t == b.t; }), | ||||
|                hits.end()); | ||||
| 
 | ||||
|     //  Convert the igl::Hit into hit_result
 | ||||
|     outs.reserve(hits.size()); | ||||
|     for (const igl::Hit& hit : hits) { | ||||
|         outs.emplace_back(EigenMesh3D::hit_result(*this)); | ||||
|         outs.emplace_back(IndexedMesh::hit_result(*this)); | ||||
|         outs.back().m_t = double(hit.t); | ||||
|         outs.back().m_dir = dir; | ||||
|         outs.back().m_source = s; | ||||
|  | @ -355,8 +185,8 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const | |||
| 
 | ||||
| 
 | ||||
| #ifdef SLIC3R_HOLE_RAYCASTER | ||||
| EigenMesh3D::hit_result EigenMesh3D::filter_hits( | ||||
|                      const std::vector<EigenMesh3D::hit_result>& object_hits) const | ||||
| IndexedMesh::hit_result IndexedMesh::filter_hits( | ||||
|     const std::vector<IndexedMesh::hit_result>& object_hits) const | ||||
| { | ||||
|     assert(! m_holes.empty()); | ||||
|     hit_result out(*this); | ||||
|  | @ -377,7 +207,7 @@ EigenMesh3D::hit_result EigenMesh3D::filter_hits( | |||
|     }; | ||||
|     std::vector<HoleHit> hole_isects; | ||||
|     hole_isects.reserve(m_holes.size()); | ||||
|      | ||||
| 
 | ||||
|     auto sf = s.cast<float>(); | ||||
|     auto dirf = dir.cast<float>(); | ||||
| 
 | ||||
|  | @ -452,7 +282,7 @@ EigenMesh3D::hit_result EigenMesh3D::filter_hits( | |||
| #endif | ||||
| 
 | ||||
| 
 | ||||
| double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const { | ||||
| double IndexedMesh::squared_distance(const Vec3d &p, int& i, Vec3d& c) const { | ||||
|     double sqdst = 0; | ||||
|     Eigen::Matrix<double, 1, 3> pp = p; | ||||
|     Eigen::Matrix<double, 1, 3> cc; | ||||
|  | @ -461,31 +291,19 @@ double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const { | |||
|     return sqdst; | ||||
| } | ||||
| 
 | ||||
| /* ****************************************************************************
 | ||||
|  * Misc functions | ||||
|  * ****************************************************************************/ | ||||
| 
 | ||||
| namespace  { | ||||
| 
 | ||||
| bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2, | ||||
|                    double eps = 0.05) | ||||
| static bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2, | ||||
|                           double eps = 0.05) | ||||
| { | ||||
|     using Line3D = Eigen::ParametrizedLine<double, 3>; | ||||
|      | ||||
| 
 | ||||
|     auto line = Line3D::Through(e1, e2); | ||||
|     double d = line.distance(p); | ||||
|     return std::abs(d) < eps; | ||||
| } | ||||
| 
 | ||||
| template<class Vec> double distance(const Vec& pp1, const Vec& pp2) { | ||||
|     auto p = pp2 - pp1; | ||||
|     return std::sqrt(p.transpose() * p); | ||||
| } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| PointSet normals(const PointSet& points, | ||||
|                  const EigenMesh3D& mesh, | ||||
|                  const IndexedMesh& mesh, | ||||
|                  double eps, | ||||
|                  std::function<void()> thr, // throw on cancel
 | ||||
|                  const std::vector<unsigned>& pt_indices) | ||||
|  | @ -531,11 +349,11 @@ PointSet normals(const PointSet& points, | |||
|             // ic will mark a single vertex.
 | ||||
|             int ia = -1, ib = -1, ic = -1; | ||||
| 
 | ||||
|             if (std::abs(distance(p, p1)) < eps) { | ||||
|             if (std::abs((p - p1).norm()) < eps) { | ||||
|                 ic = trindex(0); | ||||
|             } else if (std::abs(distance(p, p2)) < eps) { | ||||
|             } else if (std::abs((p - p2).norm()) < eps) { | ||||
|                 ic = trindex(1); | ||||
|             } else if (std::abs(distance(p, p3)) < eps) { | ||||
|             } else if (std::abs((p - p3).norm()) < eps) { | ||||
|                 ic = trindex(2); | ||||
|             } else if (point_on_edge(p, p1, p2, eps)) { | ||||
|                 ia = trindex(0); | ||||
|  | @ -612,148 +430,4 @@ PointSet normals(const PointSet& points, | |||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| namespace bgi = boost::geometry::index; | ||||
| using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >; | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2) | ||||
| { | ||||
|     return e1.second < e2.second; | ||||
| }; | ||||
| 
 | ||||
| ClusteredPoints cluster(Index3D &sindex, | ||||
|                         unsigned max_points, | ||||
|                         std::function<std::vector<PointIndexEl>( | ||||
|                             const Index3D &, const PointIndexEl &)> qfn) | ||||
| { | ||||
|     using Elems = std::vector<PointIndexEl>; | ||||
|      | ||||
|     // Recursive function for visiting all the points in a given distance to
 | ||||
|     // each other
 | ||||
|     std::function<void(Elems&, Elems&)> group = | ||||
|         [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster) | ||||
|     {         | ||||
|         for(auto& p : pts) { | ||||
|             std::vector<PointIndexEl> tmp = qfn(sindex, p); | ||||
|              | ||||
|             std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements); | ||||
|              | ||||
|             Elems newpts; | ||||
|             std::set_difference(tmp.begin(), tmp.end(), | ||||
|                                 cluster.begin(), cluster.end(), | ||||
|                                 std::back_inserter(newpts), cmp_ptidx_elements); | ||||
|              | ||||
|             int c = max_points && newpts.size() + cluster.size() > max_points? | ||||
|                         int(max_points - cluster.size()) : int(newpts.size()); | ||||
|              | ||||
|             cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c); | ||||
|             std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements); | ||||
|              | ||||
|             if(!newpts.empty() && (!max_points || cluster.size() < max_points)) | ||||
|                 group(newpts, cluster); | ||||
|         } | ||||
|     }; | ||||
|      | ||||
|     std::vector<Elems> clusters; | ||||
|     for(auto it = sindex.begin(); it != sindex.end();) { | ||||
|         Elems cluster = {}; | ||||
|         Elems pts = {*it}; | ||||
|         group(pts, cluster); | ||||
|          | ||||
|         for(auto& c : cluster) sindex.remove(c); | ||||
|         it = sindex.begin(); | ||||
|          | ||||
|         clusters.emplace_back(cluster); | ||||
|     } | ||||
|      | ||||
|     ClusteredPoints result; | ||||
|     for(auto& cluster : clusters) { | ||||
|         result.emplace_back(); | ||||
|         for(auto c : cluster) result.back().emplace_back(c.second); | ||||
|     } | ||||
|      | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex, | ||||
|                                            const PointIndexEl& p, | ||||
|                                            double dist, | ||||
|                                            unsigned max_points) | ||||
| { | ||||
|     std::vector<PointIndexEl> tmp; tmp.reserve(max_points); | ||||
|     sindex.query( | ||||
|         bgi::nearest(p.first, max_points), | ||||
|         std::back_inserter(tmp) | ||||
|         ); | ||||
|      | ||||
|     for(auto it = tmp.begin(); it < tmp.end(); ++it) | ||||
|         if(distance(p.first, it->first) > dist) it = tmp.erase(it); | ||||
|      | ||||
|     return tmp; | ||||
| } | ||||
| 
 | ||||
| } // namespace
 | ||||
| 
 | ||||
| // Clustering a set of points by the given criteria
 | ||||
| ClusteredPoints cluster( | ||||
|     const std::vector<unsigned>& indices, | ||||
|     std::function<Vec3d(unsigned)> pointfn, | ||||
|     double dist, | ||||
|     unsigned max_points) | ||||
| { | ||||
|     // A spatial index for querying the nearest points
 | ||||
|     Index3D sindex; | ||||
|      | ||||
|     // Build the index
 | ||||
|     for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); | ||||
|      | ||||
|     return cluster(sindex, max_points, | ||||
|                    [dist, max_points](const Index3D& sidx, const PointIndexEl& p) | ||||
|                    { | ||||
|                        return distance_queryfn(sidx, p, dist, max_points); | ||||
|                    }); | ||||
| } | ||||
| 
 | ||||
| // Clustering a set of points by the given criteria
 | ||||
| ClusteredPoints cluster( | ||||
|     const std::vector<unsigned>& indices, | ||||
|     std::function<Vec3d(unsigned)> pointfn, | ||||
|     std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate, | ||||
|     unsigned max_points) | ||||
| { | ||||
|     // A spatial index for querying the nearest points
 | ||||
|     Index3D sindex; | ||||
|      | ||||
|     // Build the index
 | ||||
|     for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); | ||||
|      | ||||
|     return cluster(sindex, max_points, | ||||
|                    [max_points, predicate](const Index3D& sidx, const PointIndexEl& p) | ||||
|                    { | ||||
|                        std::vector<PointIndexEl> tmp; tmp.reserve(max_points); | ||||
|                        sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){ | ||||
|                                       return predicate(p, e); | ||||
|                                   }), std::back_inserter(tmp)); | ||||
|                        return tmp; | ||||
|                    }); | ||||
| } | ||||
| 
 | ||||
| ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points) | ||||
| { | ||||
|     // A spatial index for querying the nearest points
 | ||||
|     Index3D sindex; | ||||
|      | ||||
|     // Build the index
 | ||||
|     for(Eigen::Index i = 0; i < pts.rows(); i++) | ||||
|         sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i))); | ||||
|      | ||||
|     return cluster(sindex, max_points, | ||||
|                    [dist, max_points](const Index3D& sidx, const PointIndexEl& p) | ||||
|                    { | ||||
|                        return distance_queryfn(sidx, p, dist, max_points); | ||||
|                    }); | ||||
| } | ||||
| 
 | ||||
| } // namespace sla
 | ||||
| } // namespace Slic3r
 | ||||
| }} // namespace Slic3r::sla
 | ||||
|  | @ -1,8 +1,10 @@ | |||
| #ifndef SLA_EIGENMESH3D_H | ||||
| #define SLA_EIGENMESH3D_H | ||||
| #ifndef SLA_INDEXEDMESH_H | ||||
| #define SLA_INDEXEDMESH_H | ||||
| 
 | ||||
| #include <libslic3r/SLA/Common.hpp> | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <libslic3r/Point.hpp> | ||||
| 
 | ||||
| // There is an implementation of a hole-aware raycaster that was eventually
 | ||||
| // not used in production version. It is now hidden under following define
 | ||||
|  | @ -19,10 +21,12 @@ class TriangleMesh; | |||
| 
 | ||||
| namespace sla { | ||||
| 
 | ||||
| using PointSet = Eigen::MatrixXd; | ||||
| 
 | ||||
| /// An index-triangle structure for libIGL functions. Also serves as an
 | ||||
| /// alternative (raw) input format for the SLASupportTree.
 | ||||
| //  Implemented in libslic3r/SLA/Common.cpp
 | ||||
| class EigenMesh3D { | ||||
| class IndexedMesh { | ||||
|     class AABBImpl; | ||||
|      | ||||
|     const TriangleMesh* m_tm; | ||||
|  | @ -38,15 +42,15 @@ class EigenMesh3D { | |||
| 
 | ||||
| public: | ||||
|      | ||||
|     explicit EigenMesh3D(const TriangleMesh&); | ||||
|     explicit IndexedMesh(const TriangleMesh&); | ||||
|      | ||||
|     EigenMesh3D(const EigenMesh3D& other); | ||||
|     EigenMesh3D& operator=(const EigenMesh3D&); | ||||
|     IndexedMesh(const IndexedMesh& other); | ||||
|     IndexedMesh& operator=(const IndexedMesh&); | ||||
|      | ||||
|     EigenMesh3D(EigenMesh3D &&other); | ||||
|     EigenMesh3D& operator=(EigenMesh3D &&other); | ||||
|     IndexedMesh(IndexedMesh &&other); | ||||
|     IndexedMesh& operator=(IndexedMesh &&other); | ||||
|      | ||||
|     ~EigenMesh3D(); | ||||
|     ~IndexedMesh(); | ||||
|      | ||||
|     inline double ground_level() const { return m_ground_level + m_gnd_offset; } | ||||
|     inline void ground_level_offset(double o) { m_gnd_offset = o; } | ||||
|  | @ -62,15 +66,15 @@ public: | |||
|         // m_t holds a distance from m_source to the intersection.
 | ||||
|         double m_t = infty(); | ||||
|         int m_face_id = -1; | ||||
|         const EigenMesh3D *m_mesh = nullptr; | ||||
|         const IndexedMesh *m_mesh = nullptr; | ||||
|         Vec3d m_dir; | ||||
|         Vec3d m_source; | ||||
|         Vec3d m_normal; | ||||
|         friend class EigenMesh3D; | ||||
|         friend class IndexedMesh; | ||||
|          | ||||
|         // A valid object of this class can only be obtained from
 | ||||
|         // EigenMesh3D::query_ray_hit method.
 | ||||
|         explicit inline hit_result(const EigenMesh3D& em): m_mesh(&em) {} | ||||
|         // IndexedMesh::query_ray_hit method.
 | ||||
|         explicit inline hit_result(const IndexedMesh& em): m_mesh(&em) {} | ||||
|     public: | ||||
|         // This denotes no hit on the mesh.
 | ||||
|         static inline constexpr double infty() { return std::numeric_limits<double>::infinity(); } | ||||
|  | @ -83,7 +87,7 @@ public: | |||
|         inline Vec3d position() const { return m_source + m_dir * m_t; } | ||||
|         inline int face() const { return m_face_id; } | ||||
|         inline bool is_valid() const { return m_mesh != nullptr; } | ||||
|         inline bool is_hit() const { return !std::isinf(m_t); } | ||||
|         inline bool is_hit() const { return m_face_id >= 0 && !std::isinf(m_t); } | ||||
| 
 | ||||
|         inline const Vec3d& normal() const { | ||||
|             assert(is_valid()); | ||||
|  | @ -107,7 +111,7 @@ public: | |||
|     // This function is currently not used anywhere, it was written when the
 | ||||
|     // holes were subtracted on slices, that is, before we started using CGAL
 | ||||
|     // to actually cut the holes into the mesh.
 | ||||
|     hit_result filter_hits(const std::vector<EigenMesh3D::hit_result>& obj_hits) const; | ||||
|     hit_result filter_hits(const std::vector<IndexedMesh::hit_result>& obj_hits) const; | ||||
| #endif | ||||
| 
 | ||||
|     // Casting a ray on the mesh, returns the distance where the hit occures.
 | ||||
|  | @ -125,16 +129,18 @@ public: | |||
|     } | ||||
| 
 | ||||
|     Vec3d normal_by_face_id(int face_id) const; | ||||
| 
 | ||||
|     const TriangleMesh * get_triangle_mesh() const { return m_tm; } | ||||
| }; | ||||
| 
 | ||||
| // Calculate the normals for the selected points (from 'points' set) on the
 | ||||
| // mesh. This will call squared distance for each point.
 | ||||
| PointSet normals(const PointSet& points, | ||||
|     const EigenMesh3D& convert_mesh, | ||||
|     const IndexedMesh& convert_mesh, | ||||
|     double eps = 0.05,  // min distance from edges
 | ||||
|     std::function<void()> throw_on_cancel = [](){}, | ||||
|     const std::vector<unsigned>& selected_points = {}); | ||||
| 
 | ||||
| }} // namespace Slic3r::sla
 | ||||
| 
 | ||||
| #endif // EIGENMESH3D_H
 | ||||
| #endif // INDEXEDMESH_H
 | ||||
|  | @ -2,6 +2,7 @@ | |||
| #define SLA_JOBCONTROLLER_HPP | ||||
| 
 | ||||
| #include <functional> | ||||
| #include <string> | ||||
| 
 | ||||
| namespace Slic3r { namespace sla { | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| #include <libslic3r/SLA/Pad.hpp> | ||||
| #include <libslic3r/SLA/Common.hpp> | ||||
| #include <libslic3r/SLA/SpatIndex.hpp> | ||||
| #include <libslic3r/SLA/BoostAdapter.hpp> | ||||
| #include <libslic3r/SLA/Contour3D.hpp> | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ | |||
| #include "libslic3r/Point.hpp" | ||||
| #include "SupportPoint.hpp" | ||||
| #include "Hollowing.hpp" | ||||
| #include "EigenMesh3D.hpp" | ||||
| #include "IndexedMesh.hpp" | ||||
| #include "libslic3r/Model.hpp" | ||||
| 
 | ||||
| #include <tbb/parallel_for.h> | ||||
|  | @ -15,7 +15,7 @@ template<class Pt> Vec3d pos(const Pt &p) { return p.pos.template cast<double>() | |||
| template<class Pt> void pos(Pt &p, const Vec3d &pp) { p.pos = pp.cast<float>(); } | ||||
| 
 | ||||
| template<class PointType> | ||||
| void reproject_support_points(const EigenMesh3D &mesh, std::vector<PointType> &pts) | ||||
| void reproject_support_points(const IndexedMesh &mesh, std::vector<PointType> &pts) | ||||
| { | ||||
|     tbb::parallel_for(size_t(0), pts.size(), [&mesh, &pts](size_t idx) { | ||||
|         int junk; | ||||
|  | @ -40,7 +40,7 @@ inline void reproject_points_and_holes(ModelObject *object) | |||
| 
 | ||||
|     TriangleMesh rmsh = object->raw_mesh(); | ||||
|     rmsh.require_shared_vertices(); | ||||
|     EigenMesh3D emesh{rmsh}; | ||||
|     IndexedMesh emesh{rmsh}; | ||||
| 
 | ||||
|     if (has_sppoints) | ||||
|         reproject_support_points(emesh, object->sla_support_points); | ||||
|  |  | |||
|  | @ -2,7 +2,6 @@ | |||
| #include <exception> | ||||
| 
 | ||||
| #include <libnest2d/optimizers/nlopt/genetic.hpp> | ||||
| #include <libslic3r/SLA/Common.hpp> | ||||
| #include <libslic3r/SLA/Rotfinder.hpp> | ||||
| #include <libslic3r/SLA/SupportTree.hpp> | ||||
| #include "Model.hpp" | ||||
|  |  | |||
							
								
								
									
										161
									
								
								src/libslic3r/SLA/SpatIndex.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								src/libslic3r/SLA/SpatIndex.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,161 @@ | |||
| #include "SpatIndex.hpp" | ||||
| 
 | ||||
| // for concave hull merging decisions
 | ||||
| #include <libslic3r/SLA/BoostAdapter.hpp> | ||||
| 
 | ||||
| #ifdef _MSC_VER | ||||
| #pragma warning(push) | ||||
| #pragma warning(disable: 4244) | ||||
| #pragma warning(disable: 4267) | ||||
| #endif | ||||
| 
 | ||||
| #include "boost/geometry/index/rtree.hpp" | ||||
| 
 | ||||
| #ifdef _MSC_VER | ||||
| #pragma warning(pop) | ||||
| #endif | ||||
| 
 | ||||
| namespace Slic3r { namespace sla { | ||||
| 
 | ||||
| /* **************************************************************************
 | ||||
|  * PointIndex implementation | ||||
|  * ************************************************************************** */ | ||||
| 
 | ||||
| class PointIndex::Impl { | ||||
| public: | ||||
|     using BoostIndex = boost::geometry::index::rtree< PointIndexEl, | ||||
|                                                      boost::geometry::index::rstar<16, 4> /* ? */ >; | ||||
| 
 | ||||
|     BoostIndex m_store; | ||||
| }; | ||||
| 
 | ||||
| PointIndex::PointIndex(): m_impl(new Impl()) {} | ||||
| PointIndex::~PointIndex() {} | ||||
| 
 | ||||
| PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} | ||||
| PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} | ||||
| 
 | ||||
| PointIndex& PointIndex::operator=(const PointIndex &cpy) | ||||
| { | ||||
|     m_impl.reset(new Impl(*cpy.m_impl)); | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| PointIndex& PointIndex::operator=(PointIndex &&cpy) | ||||
| { | ||||
|     m_impl.swap(cpy.m_impl); | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| void PointIndex::insert(const PointIndexEl &el) | ||||
| { | ||||
|     m_impl->m_store.insert(el); | ||||
| } | ||||
| 
 | ||||
| bool PointIndex::remove(const PointIndexEl& el) | ||||
| { | ||||
|     return m_impl->m_store.remove(el) == 1; | ||||
| } | ||||
| 
 | ||||
| std::vector<PointIndexEl> | ||||
| PointIndex::query(std::function<bool(const PointIndexEl &)> fn) const | ||||
| { | ||||
|     namespace bgi = boost::geometry::index; | ||||
| 
 | ||||
|     std::vector<PointIndexEl> ret; | ||||
|     m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret)); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) const | ||||
| { | ||||
|     namespace bgi = boost::geometry::index; | ||||
|     std::vector<PointIndexEl> ret; ret.reserve(k); | ||||
|     m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret)); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| size_t PointIndex::size() const | ||||
| { | ||||
|     return m_impl->m_store.size(); | ||||
| } | ||||
| 
 | ||||
| void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) | ||||
| { | ||||
|     for(auto& el : m_impl->m_store) fn(el); | ||||
| } | ||||
| 
 | ||||
| void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) const | ||||
| { | ||||
|     for(const auto &el : m_impl->m_store) fn(el); | ||||
| } | ||||
| 
 | ||||
| /* **************************************************************************
 | ||||
|  * BoxIndex implementation | ||||
|  * ************************************************************************** */ | ||||
| 
 | ||||
| class BoxIndex::Impl { | ||||
| public: | ||||
|     using BoostIndex = boost::geometry::index:: | ||||
|         rtree<BoxIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>; | ||||
| 
 | ||||
|     BoostIndex m_store; | ||||
| }; | ||||
| 
 | ||||
| BoxIndex::BoxIndex(): m_impl(new Impl()) {} | ||||
| BoxIndex::~BoxIndex() {} | ||||
| 
 | ||||
| BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} | ||||
| BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} | ||||
| 
 | ||||
| BoxIndex& BoxIndex::operator=(const BoxIndex &cpy) | ||||
| { | ||||
|     m_impl.reset(new Impl(*cpy.m_impl)); | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| BoxIndex& BoxIndex::operator=(BoxIndex &&cpy) | ||||
| { | ||||
|     m_impl.swap(cpy.m_impl); | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| void BoxIndex::insert(const BoxIndexEl &el) | ||||
| { | ||||
|     m_impl->m_store.insert(el); | ||||
| } | ||||
| 
 | ||||
| bool BoxIndex::remove(const BoxIndexEl& el) | ||||
| { | ||||
|     return m_impl->m_store.remove(el) == 1; | ||||
| } | ||||
| 
 | ||||
| std::vector<BoxIndexEl> BoxIndex::query(const BoundingBox &qrbb, | ||||
|                                         BoxIndex::QueryType qt) | ||||
| { | ||||
|     namespace bgi = boost::geometry::index; | ||||
| 
 | ||||
|     std::vector<BoxIndexEl> ret; ret.reserve(m_impl->m_store.size()); | ||||
| 
 | ||||
|     switch (qt) { | ||||
|     case qtIntersects: | ||||
|         m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret)); | ||||
|         break; | ||||
|     case qtWithin: | ||||
|         m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret)); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| size_t BoxIndex::size() const | ||||
| { | ||||
|     return m_impl->m_store.size(); | ||||
| } | ||||
| 
 | ||||
| void BoxIndex::foreach(std::function<void (const BoxIndexEl &)> fn) | ||||
| { | ||||
|     for(auto& el : m_impl->m_store) fn(el); | ||||
| } | ||||
| 
 | ||||
| }} // namespace Slic3r::sla
 | ||||
|  | @ -73,7 +73,7 @@ public: | |||
|     BoxIndex& operator=(BoxIndex&&); | ||||
|      | ||||
|     void insert(const BoxIndexEl&); | ||||
|     inline void insert(const BoundingBox& bb, unsigned idx) | ||||
|     void insert(const BoundingBox& bb, unsigned idx) | ||||
|     { | ||||
|         insert(std::make_pair(bb, unsigned(idx))); | ||||
|     } | ||||
|  |  | |||
|  | @ -2,7 +2,6 @@ | |||
| #define SLA_SUPPORTPOINT_HPP | ||||
| 
 | ||||
| #include <vector> | ||||
| #include <libslic3r/SLA/Common.hpp> | ||||
| #include <libslic3r/ExPolygon.hpp> | ||||
| 
 | ||||
| namespace Slic3r { namespace sla { | ||||
|  | @ -29,13 +28,13 @@ struct SupportPoint | |||
|                  float pos_y, | ||||
|                  float pos_z, | ||||
|                  float head_radius, | ||||
|                  bool  new_island) | ||||
|                  bool  new_island = false) | ||||
|         : pos(pos_x, pos_y, pos_z) | ||||
|         , head_front_radius(head_radius) | ||||
|         , is_new_island(new_island) | ||||
|     {} | ||||
|      | ||||
|     SupportPoint(Vec3f position, float head_radius, bool new_island) | ||||
|     SupportPoint(Vec3f position, float head_radius, bool new_island = false) | ||||
|         : pos(position) | ||||
|         , head_front_radius(head_radius) | ||||
|         , is_new_island(new_island) | ||||
|  |  | |||
|  | @ -50,7 +50,7 @@ float SupportPointGenerator::distance_limit(float angle) const | |||
| }*/ | ||||
| 
 | ||||
| SupportPointGenerator::SupportPointGenerator( | ||||
|         const sla::EigenMesh3D &emesh, | ||||
|         const sla::IndexedMesh &emesh, | ||||
|         const std::vector<ExPolygons> &slices, | ||||
|         const std::vector<float> &     heights, | ||||
|         const Config &                 config, | ||||
|  | @ -64,7 +64,7 @@ SupportPointGenerator::SupportPointGenerator( | |||
| } | ||||
| 
 | ||||
| SupportPointGenerator::SupportPointGenerator( | ||||
|         const EigenMesh3D &emesh, | ||||
|         const IndexedMesh &emesh, | ||||
|         const SupportPointGenerator::Config &config, | ||||
|         std::function<void ()> throw_on_cancel,  | ||||
|         std::function<void (int)> statusfn) | ||||
|  | @ -95,8 +95,8 @@ void SupportPointGenerator::project_onto_mesh(std::vector<sla::SupportPoint>& po | |||
|                     m_throw_on_cancel(); | ||||
|                 Vec3f& p = points[point_id].pos; | ||||
|                 // Project the point upward and downward and choose the closer intersection with the mesh.
 | ||||
|                 sla::EigenMesh3D::hit_result hit_up   = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., 1.)); | ||||
|                 sla::EigenMesh3D::hit_result hit_down = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., -1.)); | ||||
|                 sla::IndexedMesh::hit_result hit_up   = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., 1.)); | ||||
|                 sla::IndexedMesh::hit_result hit_down = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., -1.)); | ||||
| 
 | ||||
|                 bool up   = hit_up.is_hit(); | ||||
|                 bool down = hit_down.is_hit(); | ||||
|  | @ -104,7 +104,7 @@ void SupportPointGenerator::project_onto_mesh(std::vector<sla::SupportPoint>& po | |||
|                 if (!up && !down) | ||||
|                     continue; | ||||
| 
 | ||||
|                 sla::EigenMesh3D::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down; | ||||
|                 sla::IndexedMesh::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down; | ||||
|                 p = p + (hit.distance() * hit.direction()).cast<float>(); | ||||
|             } | ||||
|         }); | ||||
|  | @ -523,15 +523,12 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance) | ||||
| void remove_bottom_points(std::vector<SupportPoint> &pts, float lvl) | ||||
| { | ||||
|     // get iterator to the reorganized vector end
 | ||||
|     auto endit = | ||||
|         std::remove_if(pts.begin(), pts.end(), | ||||
|                        [tolerance, gnd_lvl](const sla::SupportPoint &sp) { | ||||
|         double diff = std::abs(gnd_lvl - | ||||
|                                double(sp.pos(Z))); | ||||
|         return diff <= tolerance; | ||||
|     auto endit = std::remove_if(pts.begin(), pts.end(), [lvl] | ||||
|                                 (const sla::SupportPoint &sp) { | ||||
|         return sp.pos.z() <= lvl; | ||||
|     }); | ||||
| 
 | ||||
|     // erase all elements after the new end
 | ||||
|  |  | |||
|  | @ -3,9 +3,8 @@ | |||
| 
 | ||||
| #include <random> | ||||
| 
 | ||||
| #include <libslic3r/SLA/Common.hpp> | ||||
| #include <libslic3r/SLA/SupportPoint.hpp> | ||||
| #include <libslic3r/SLA/EigenMesh3D.hpp> | ||||
| #include <libslic3r/SLA/IndexedMesh.hpp> | ||||
| 
 | ||||
| #include <libslic3r/BoundingBox.hpp> | ||||
| #include <libslic3r/ClipperUtils.hpp> | ||||
|  | @ -28,10 +27,10 @@ public: | |||
|         inline float tear_pressure() const { return 1.f; }  // pressure that the display exerts    (the force unit per mm2)
 | ||||
|     }; | ||||
|      | ||||
|     SupportPointGenerator(const EigenMesh3D& emesh, const std::vector<ExPolygons>& slices, | ||||
|     SupportPointGenerator(const IndexedMesh& emesh, const std::vector<ExPolygons>& slices, | ||||
|                     const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn); | ||||
|      | ||||
|     SupportPointGenerator(const EigenMesh3D& emesh, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn); | ||||
|     SupportPointGenerator(const IndexedMesh& emesh, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn); | ||||
|      | ||||
|     const std::vector<SupportPoint>& output() const { return m_output; } | ||||
|     std::vector<SupportPoint>& output() { return m_output; } | ||||
|  | @ -207,14 +206,14 @@ private: | |||
|     static void output_structures(const std::vector<Structure> &structures); | ||||
| #endif // SLA_SUPPORTPOINTGEN_DEBUG
 | ||||
|      | ||||
|     const EigenMesh3D& m_emesh; | ||||
|     const IndexedMesh& m_emesh; | ||||
|     std::function<void(void)> m_throw_on_cancel; | ||||
|     std::function<void(int)>  m_statusfn; | ||||
|      | ||||
|     std::mt19937 m_rng; | ||||
| }; | ||||
| 
 | ||||
| void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance); | ||||
| void remove_bottom_points(std::vector<SupportPoint> &pts, float lvl); | ||||
| 
 | ||||
| }} // namespace Slic3r::sla
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -5,9 +5,9 @@ | |||
| 
 | ||||
| #include <numeric> | ||||
| #include <libslic3r/SLA/SupportTree.hpp> | ||||
| #include <libslic3r/SLA/Common.hpp> | ||||
| #include <libslic3r/SLA/SpatIndex.hpp> | ||||
| #include <libslic3r/SLA/SupportTreeBuilder.hpp> | ||||
| #include <libslic3r/SLA/SupportTreeBuildsteps.hpp> | ||||
| 
 | ||||
| #include <libslic3r/MTUtils.hpp> | ||||
| #include <libslic3r/ClipperUtils.hpp> | ||||
|  | @ -28,20 +28,6 @@ | |||
| namespace Slic3r { | ||||
| namespace sla { | ||||
| 
 | ||||
| // Compile time configuration value definitions:
 | ||||
| 
 | ||||
| // The max Z angle for a normal at which it will get completely ignored.
 | ||||
| const double SupportConfig::normal_cutoff_angle = 150.0 * M_PI / 180.0; | ||||
| 
 | ||||
| // The shortest distance of any support structure from the model surface
 | ||||
| const double SupportConfig::safety_distance_mm = 0.5; | ||||
| 
 | ||||
| const double SupportConfig::max_solo_pillar_height_mm = 15.0; | ||||
| const double SupportConfig::max_dual_pillar_height_mm = 35.0; | ||||
| const double   SupportConfig::optimizer_rel_score_diff = 1e-6; | ||||
| const unsigned SupportConfig::optimizer_max_iterations = 1000; | ||||
| const unsigned SupportConfig::pillar_cascade_neighbors = 3; | ||||
| 
 | ||||
| void SupportTree::retrieve_full_mesh(TriangleMesh &outmesh) const { | ||||
|     outmesh.merge(retrieve_mesh(MeshType::Support)); | ||||
|     outmesh.merge(retrieve_mesh(MeshType::Pad)); | ||||
|  | @ -103,9 +89,11 @@ SupportTree::UPtr SupportTree::create(const SupportableMesh &sm, | |||
|     builder->m_ctl = ctl; | ||||
|      | ||||
|     if (sm.cfg.enabled) { | ||||
|         builder->build(sm); | ||||
|         // Execute takes care about the ground_level
 | ||||
|         SupportTreeBuildsteps::execute(*builder, sm); | ||||
|         builder->merge_and_cleanup();   // clean metadata, leave only the meshes.
 | ||||
|     } else { | ||||
|         // If a pad gets added later, it will be in the right Z level
 | ||||
|         builder->ground_level = sm.emesh.ground_level(); | ||||
|     } | ||||
|      | ||||
|  |  | |||
|  | @ -5,9 +5,8 @@ | |||
| #include <memory> | ||||
| #include <Eigen/Geometry> | ||||
| 
 | ||||
| #include <libslic3r/SLA/Common.hpp> | ||||
| #include <libslic3r/SLA/Pad.hpp> | ||||
| #include <libslic3r/SLA/EigenMesh3D.hpp> | ||||
| #include <libslic3r/SLA/IndexedMesh.hpp> | ||||
| #include <libslic3r/SLA/SupportPoint.hpp> | ||||
| #include <libslic3r/SLA/JobController.hpp> | ||||
| 
 | ||||
|  | @ -32,7 +31,7 @@ enum class PillarConnectionMode | |||
|     dynamic | ||||
| }; | ||||
| 
 | ||||
| struct SupportConfig | ||||
| struct SupportTreeConfig | ||||
| { | ||||
|     bool   enabled = true; | ||||
|      | ||||
|  | @ -45,6 +44,8 @@ struct SupportConfig | |||
|     // Radius of the back side of the 3d arrow.
 | ||||
|     double head_back_radius_mm = 0.5; | ||||
| 
 | ||||
|     double head_fallback_radius_mm = 0.25; | ||||
| 
 | ||||
|     // Width in mm from the back sphere center to the front sphere center.
 | ||||
|     double head_width_mm = 1.0; | ||||
| 
 | ||||
|  | @ -95,36 +96,43 @@ struct SupportConfig | |||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
|     // The max Z angle for a normal at which it will get completely ignored.
 | ||||
|     static const double normal_cutoff_angle; | ||||
|     static const double constexpr normal_cutoff_angle = 150.0 * M_PI / 180.0; | ||||
| 
 | ||||
|     // The shortest distance of any support structure from the model surface
 | ||||
|     static const double safety_distance_mm; | ||||
|     static const double constexpr safety_distance_mm = 0.5; | ||||
| 
 | ||||
|     static const double max_solo_pillar_height_mm; | ||||
|     static const double max_dual_pillar_height_mm; | ||||
|     static const double   optimizer_rel_score_diff; | ||||
|     static const unsigned optimizer_max_iterations; | ||||
|     static const unsigned pillar_cascade_neighbors; | ||||
|     static const double constexpr max_solo_pillar_height_mm = 15.0; | ||||
|     static const double constexpr max_dual_pillar_height_mm = 35.0; | ||||
|     static const double constexpr optimizer_rel_score_diff = 1e-6; | ||||
|     static const unsigned constexpr optimizer_max_iterations = 1000; | ||||
|     static const unsigned constexpr pillar_cascade_neighbors = 3; | ||||
|      | ||||
| }; | ||||
| 
 | ||||
| // TODO: Part of future refactor
 | ||||
| //class SupportConfig {
 | ||||
| //    std::optional<SupportTreeConfig> tree_cfg {std::in_place_t{}}; // fill up
 | ||||
| //    std::optional<PadConfig>         pad_cfg;
 | ||||
| //};
 | ||||
| 
 | ||||
| enum class MeshType { Support, Pad }; | ||||
| 
 | ||||
| struct SupportableMesh | ||||
| { | ||||
|     EigenMesh3D   emesh; | ||||
|     IndexedMesh  emesh; | ||||
|     SupportPoints pts; | ||||
|     SupportConfig cfg; | ||||
|     SupportTreeConfig cfg; | ||||
|     PadConfig     pad_cfg; | ||||
| 
 | ||||
|     explicit SupportableMesh(const TriangleMesh & trmsh, | ||||
|                              const SupportPoints &sp, | ||||
|                              const SupportConfig &c) | ||||
|                              const SupportTreeConfig &c) | ||||
|         : emesh{trmsh}, pts{sp}, cfg{c} | ||||
|     {} | ||||
|      | ||||
|     explicit SupportableMesh(const EigenMesh3D   &em, | ||||
|     explicit SupportableMesh(const IndexedMesh   &em, | ||||
|                              const SupportPoints &sp, | ||||
|                              const SupportConfig &c) | ||||
|                              const SupportTreeConfig &c) | ||||
|         : emesh{em}, pts{sp}, cfg{c} | ||||
|     {} | ||||
| }; | ||||
|  |  | |||
|  | @ -1,336 +1,26 @@ | |||
| #define NOMINMAX | ||||
| 
 | ||||
| #include <libslic3r/SLA/SupportTreeBuilder.hpp> | ||||
| #include <libslic3r/SLA/SupportTreeBuildsteps.hpp> | ||||
| #include <libslic3r/SLA/SupportTreeMesher.hpp> | ||||
| #include <libslic3r/SLA/Contour3D.hpp> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace sla { | ||||
| 
 | ||||
| Contour3D sphere(double rho, Portion portion, double fa) { | ||||
|      | ||||
|     Contour3D ret; | ||||
|      | ||||
|     // prohibit close to zero radius
 | ||||
|     if(rho <= 1e-6 && rho >= -1e-6) return ret; | ||||
|      | ||||
|     auto& vertices = ret.points; | ||||
|     auto& facets = ret.faces3; | ||||
|      | ||||
|     // Algorithm:
 | ||||
|     // Add points one-by-one to the sphere grid and form facets using relative
 | ||||
|     // coordinates. Sphere is composed effectively of a mesh of stacked circles.
 | ||||
|      | ||||
|     // adjust via rounding to get an even multiple for any provided angle.
 | ||||
|     double angle = (2*PI / floor(2*PI / fa)); | ||||
|      | ||||
|     // Ring to be scaled to generate the steps of the sphere
 | ||||
|     std::vector<double> ring; | ||||
|      | ||||
|     for (double i = 0; i < 2*PI; i+=angle) ring.emplace_back(i); | ||||
|      | ||||
|     const auto sbegin = size_t(2*std::get<0>(portion)/angle); | ||||
|     const auto send = size_t(2*std::get<1>(portion)/angle); | ||||
|      | ||||
|     const size_t steps = ring.size(); | ||||
|     const double increment = 1.0 / double(steps); | ||||
|      | ||||
|     // special case: first ring connects to 0,0,0
 | ||||
|     // insert and form facets.
 | ||||
|     if(sbegin == 0) | ||||
|         vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*sbegin*2.0*rho)); | ||||
|      | ||||
|     auto id = coord_t(vertices.size()); | ||||
|     for (size_t i = 0; i < ring.size(); i++) { | ||||
|         // Fixed scaling
 | ||||
|         const double z = -rho + increment*rho*2.0 * (sbegin + 1.0); | ||||
|         // radius of the circle for this step.
 | ||||
|         const double r = std::sqrt(std::abs(rho*rho - z*z)); | ||||
|         Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); | ||||
|         vertices.emplace_back(Vec3d(b(0), b(1), z)); | ||||
|          | ||||
|         if (sbegin == 0) | ||||
|             (i == 0) ? facets.emplace_back(coord_t(ring.size()), 0, 1) : | ||||
|         			   facets.emplace_back(id - 1, 0, id); | ||||
|         ++id; | ||||
|     } | ||||
|      | ||||
|     // General case: insert and form facets for each step,
 | ||||
|     // joining it to the ring below it.
 | ||||
|     for (size_t s = sbegin + 2; s < send - 1; s++) { | ||||
|         const double z = -rho + increment*double(s*2.0*rho); | ||||
|         const double r = std::sqrt(std::abs(rho*rho - z*z)); | ||||
|          | ||||
|         for (size_t i = 0; i < ring.size(); i++) { | ||||
|             Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); | ||||
|             vertices.emplace_back(Vec3d(b(0), b(1), z)); | ||||
|             auto id_ringsize = coord_t(id - int(ring.size())); | ||||
|             if (i == 0) { | ||||
|                 // wrap around
 | ||||
|                 facets.emplace_back(id - 1, id, id + coord_t(ring.size() - 1) ); | ||||
|                 facets.emplace_back(id - 1, id_ringsize, id); | ||||
|             } else { | ||||
|                 facets.emplace_back(id_ringsize - 1, id_ringsize, id); | ||||
|                 facets.emplace_back(id - 1, id_ringsize - 1, id); | ||||
|             } | ||||
|             id++; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     // special case: last ring connects to 0,0,rho*2.0
 | ||||
|     // only form facets.
 | ||||
|     if(send >= size_t(2*PI / angle)) { | ||||
|         vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*send*2.0*rho)); | ||||
|         for (size_t i = 0; i < ring.size(); i++) { | ||||
|             auto id_ringsize = coord_t(id - int(ring.size())); | ||||
|             if (i == 0) { | ||||
|                 // third vertex is on the other side of the ring.
 | ||||
|                 facets.emplace_back(id - 1, id_ringsize, id); | ||||
|             } else { | ||||
|                 auto ci = coord_t(id_ringsize + coord_t(i)); | ||||
|                 facets.emplace_back(ci - 1, ci, id); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     id++; | ||||
|      | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp) | ||||
| { | ||||
|     Contour3D ret; | ||||
|      | ||||
|     auto steps = int(ssteps); | ||||
|     auto& points = ret.points; | ||||
|     auto& indices = ret.faces3; | ||||
|     points.reserve(2*ssteps); | ||||
|     double a = 2*PI/steps; | ||||
|      | ||||
|     Vec3d jp = sp; | ||||
|     Vec3d endp = {sp(X), sp(Y), sp(Z) + h}; | ||||
|      | ||||
|     // Upper circle points
 | ||||
|     for(int i = 0; i < steps; ++i) { | ||||
|         double phi = i*a; | ||||
|         double ex = endp(X) + r*std::cos(phi); | ||||
|         double ey = endp(Y) + r*std::sin(phi); | ||||
|         points.emplace_back(ex, ey, endp(Z)); | ||||
|     } | ||||
|      | ||||
|     // Lower circle points
 | ||||
|     for(int i = 0; i < steps; ++i) { | ||||
|         double phi = i*a; | ||||
|         double x = jp(X) + r*std::cos(phi); | ||||
|         double y = jp(Y) + r*std::sin(phi); | ||||
|         points.emplace_back(x, y, jp(Z)); | ||||
|     } | ||||
|      | ||||
|     // Now create long triangles connecting upper and lower circles
 | ||||
|     indices.reserve(2*ssteps); | ||||
|     auto offs = steps; | ||||
|     for(int i = 0; i < steps - 1; ++i) { | ||||
|         indices.emplace_back(i, i + offs, offs + i + 1); | ||||
|         indices.emplace_back(i, offs + i + 1, i + 1); | ||||
|     } | ||||
|      | ||||
|     // Last triangle connecting the first and last vertices
 | ||||
|     auto last = steps - 1; | ||||
|     indices.emplace_back(0, last, offs); | ||||
|     indices.emplace_back(last, offs + last, offs); | ||||
|      | ||||
|     // According to the slicing algorithms, we need to aid them with generating
 | ||||
|     // a watertight body. So we create a triangle fan for the upper and lower
 | ||||
|     // ending of the cylinder to close the geometry.
 | ||||
|     points.emplace_back(jp); int ci = int(points.size() - 1); | ||||
|     for(int i = 0; i < steps - 1; ++i) | ||||
|         indices.emplace_back(i + offs + 1, i + offs, ci); | ||||
|      | ||||
|     indices.emplace_back(offs, steps + offs - 1, ci); | ||||
|      | ||||
|     points.emplace_back(endp); ci = int(points.size() - 1); | ||||
|     for(int i = 0; i < steps - 1; ++i) | ||||
|         indices.emplace_back(ci, i, i + 1); | ||||
|      | ||||
|     indices.emplace_back(steps - 1, 0, ci); | ||||
|      | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| Head::Head(double       r_big_mm, | ||||
|            double       r_small_mm, | ||||
|            double       length_mm, | ||||
|            double       penetration, | ||||
|            const Vec3d &direction, | ||||
|            const Vec3d &offset, | ||||
|            const size_t circlesteps) | ||||
|     : steps(circlesteps) | ||||
|     , dir(direction) | ||||
|     , tr(offset) | ||||
|            const Vec3d &offset) | ||||
|     : dir(direction) | ||||
|     , pos(offset) | ||||
|     , r_back_mm(r_big_mm) | ||||
|     , r_pin_mm(r_small_mm) | ||||
|     , width_mm(length_mm) | ||||
|     , penetration_mm(penetration) | ||||
| { | ||||
|     assert(width_mm > 0.); | ||||
|     assert(r_back_mm > 0.); | ||||
|     assert(r_pin_mm > 0.); | ||||
|      | ||||
|     // We create two spheres which will be connected with a robe that fits
 | ||||
|     // both circles perfectly.
 | ||||
|      | ||||
|     // Set up the model detail level
 | ||||
|     const double detail = 2*PI/steps; | ||||
|      | ||||
|     // We don't generate whole circles. Instead, we generate only the
 | ||||
|     // portions which are visible (not covered by the robe) To know the
 | ||||
|     // exact portion of the bottom and top circles we need to use some
 | ||||
|     // rules of tangent circles from which we can derive (using simple
 | ||||
|     // triangles the following relations:
 | ||||
|      | ||||
|     // The height of the whole mesh
 | ||||
|     const double h = r_big_mm + r_small_mm + width_mm; | ||||
|     double phi = PI/2 - std::acos( (r_big_mm - r_small_mm) / h ); | ||||
|      | ||||
|     // To generate a whole circle we would pass a portion of (0, Pi)
 | ||||
|     // To generate only a half horizontal circle we can pass (0, Pi/2)
 | ||||
|     // The calculated phi is an offset to the half circles needed to smooth
 | ||||
|     // the transition from the circle to the robe geometry
 | ||||
|      | ||||
|     auto&& s1 = sphere(r_big_mm, make_portion(0, PI/2 + phi), detail); | ||||
|     auto&& s2 = sphere(r_small_mm, make_portion(PI/2 + phi, PI), detail); | ||||
|      | ||||
|     for(auto& p : s2.points) p.z() += h; | ||||
|      | ||||
|     mesh.merge(s1); | ||||
|     mesh.merge(s2); | ||||
|      | ||||
|     for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); | ||||
|         idx1 < s1.points.size() - 1; | ||||
|         idx1++, idx2++) | ||||
|     { | ||||
|         coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2); | ||||
|         coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1; | ||||
|          | ||||
|         mesh.faces3.emplace_back(i1s1, i2s1, i2s2); | ||||
|         mesh.faces3.emplace_back(i1s1, i2s2, i1s2); | ||||
|     } | ||||
|      | ||||
|     auto i1s1 = coord_t(s1.points.size()) - coord_t(steps); | ||||
|     auto i2s1 = coord_t(s1.points.size()) - 1; | ||||
|     auto i1s2 = coord_t(s1.points.size()); | ||||
|     auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1; | ||||
|      | ||||
|     mesh.faces3.emplace_back(i2s2, i2s1, i1s1); | ||||
|     mesh.faces3.emplace_back(i1s2, i2s2, i1s1); | ||||
|      | ||||
|     // To simplify further processing, we translate the mesh so that the
 | ||||
|     // last vertex of the pointing sphere (the pinpoint) will be at (0,0,0)
 | ||||
|     for(auto& p : mesh.points) p.z() -= (h + r_small_mm - penetration_mm); | ||||
| } | ||||
| 
 | ||||
| Pillar::Pillar(const Vec3d &jp, const Vec3d &endp, double radius, size_t st): | ||||
|     r(radius), steps(st), endpt(endp), starts_from_head(false) | ||||
| { | ||||
|     assert(steps > 0); | ||||
|      | ||||
|     height = jp(Z) - endp(Z); | ||||
|     if(height > EPSILON) { // Endpoint is below the starting point
 | ||||
|          | ||||
|         // We just create a bridge geometry with the pillar parameters and
 | ||||
|         // move the data.
 | ||||
|         Contour3D body = cylinder(radius, height, st, endp); | ||||
|         mesh.points.swap(body.points); | ||||
|         mesh.faces3.swap(body.faces3); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| Pillar &Pillar::add_base(double baseheight, double radius) | ||||
| { | ||||
|     if(baseheight <= 0) return *this; | ||||
|     if(baseheight > height) baseheight = height; | ||||
|      | ||||
|     assert(steps >= 0); | ||||
|     auto last = int(steps - 1); | ||||
|      | ||||
|     if(radius < r ) radius = r; | ||||
|      | ||||
|     double a = 2*PI/steps; | ||||
|     double z = endpt(Z) + baseheight; | ||||
|      | ||||
|     for(size_t i = 0; i < steps; ++i) { | ||||
|         double phi = i*a; | ||||
|         double x = endpt(X) + r*std::cos(phi); | ||||
|         double y = endpt(Y) + r*std::sin(phi); | ||||
|         base.points.emplace_back(x, y, z); | ||||
|     } | ||||
|      | ||||
|     for(size_t i = 0; i < steps; ++i) { | ||||
|         double phi = i*a; | ||||
|         double x = endpt(X) + radius*std::cos(phi); | ||||
|         double y = endpt(Y) + radius*std::sin(phi); | ||||
|         base.points.emplace_back(x, y, z - baseheight); | ||||
|     } | ||||
|      | ||||
|     auto ep = endpt; ep(Z) += baseheight; | ||||
|     base.points.emplace_back(endpt); | ||||
|     base.points.emplace_back(ep); | ||||
|      | ||||
|     auto& indices = base.faces3; | ||||
|     auto hcenter = int(base.points.size() - 1); | ||||
|     auto lcenter = int(base.points.size() - 2); | ||||
|     auto offs = int(steps); | ||||
|     for(int i = 0; i < last; ++i) { | ||||
|         indices.emplace_back(i, i + offs, offs + i + 1); | ||||
|         indices.emplace_back(i, offs + i + 1, i + 1); | ||||
|         indices.emplace_back(i, i + 1, hcenter); | ||||
|         indices.emplace_back(lcenter, offs + i + 1, offs + i); | ||||
|     } | ||||
|      | ||||
|     indices.emplace_back(0, last, offs); | ||||
|     indices.emplace_back(last, offs + last, offs); | ||||
|     indices.emplace_back(hcenter, last, 0); | ||||
|     indices.emplace_back(offs, offs + last, lcenter); | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| Bridge::Bridge(const Vec3d &j1, const Vec3d &j2, double r_mm, size_t steps): | ||||
|     r(r_mm), startp(j1), endp(j2) | ||||
| { | ||||
|     using Quaternion = Eigen::Quaternion<double>; | ||||
|     Vec3d dir = (j2 - j1).normalized(); | ||||
|     double d = distance(j2, j1); | ||||
|      | ||||
|     mesh = cylinder(r, d, steps); | ||||
|      | ||||
|     auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir); | ||||
|     for(auto& p : mesh.points) p = quater * p + j1; | ||||
| } | ||||
| 
 | ||||
| CompactBridge::CompactBridge(const Vec3d &sp, | ||||
|                              const Vec3d &ep, | ||||
|                              const Vec3d &n, | ||||
|                              double       r, | ||||
|                              bool         endball, | ||||
|                              size_t       steps) | ||||
| { | ||||
|     Vec3d startp = sp + r * n; | ||||
|     Vec3d dir = (ep - startp).normalized(); | ||||
|     Vec3d endp = ep - r * dir; | ||||
|      | ||||
|     Bridge br(startp, endp, r, steps); | ||||
|     mesh.merge(br.mesh); | ||||
|      | ||||
|     // now add the pins
 | ||||
|     double fa = 2*PI/steps; | ||||
|     auto upperball = sphere(r, Portion{PI / 2 - fa, PI}, fa); | ||||
|     for(auto& p : upperball.points) p += startp; | ||||
|      | ||||
|     if(endball) { | ||||
|         auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); | ||||
|         for(auto& p : lowerball.points) p += endp; | ||||
|         mesh.merge(lowerball); | ||||
|     } | ||||
|      | ||||
|     mesh.merge(upperball); | ||||
| } | ||||
| 
 | ||||
| Pad::Pad(const TriangleMesh &support_mesh, | ||||
|  | @ -368,7 +58,6 @@ SupportTreeBuilder::SupportTreeBuilder(SupportTreeBuilder &&o) | |||
|     , m_pillars{std::move(o.m_pillars)} | ||||
|     , m_bridges{std::move(o.m_bridges)} | ||||
|     , m_crossbridges{std::move(o.m_crossbridges)} | ||||
|     , m_compact_bridges{std::move(o.m_compact_bridges)} | ||||
|     , m_pad{std::move(o.m_pad)} | ||||
|     , m_meshcache{std::move(o.m_meshcache)} | ||||
|     , m_meshcache_valid{o.m_meshcache_valid} | ||||
|  | @ -382,7 +71,6 @@ SupportTreeBuilder::SupportTreeBuilder(const SupportTreeBuilder &o) | |||
|     , m_pillars{o.m_pillars} | ||||
|     , m_bridges{o.m_bridges} | ||||
|     , m_crossbridges{o.m_crossbridges} | ||||
|     , m_compact_bridges{o.m_compact_bridges} | ||||
|     , m_pad{o.m_pad} | ||||
|     , m_meshcache{o.m_meshcache} | ||||
|     , m_meshcache_valid{o.m_meshcache_valid} | ||||
|  | @ -397,7 +85,6 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(SupportTreeBuilder &&o) | |||
|     m_pillars = std::move(o.m_pillars); | ||||
|     m_bridges = std::move(o.m_bridges); | ||||
|     m_crossbridges = std::move(o.m_crossbridges); | ||||
|     m_compact_bridges = std::move(o.m_compact_bridges); | ||||
|     m_pad = std::move(o.m_pad); | ||||
|     m_meshcache = std::move(o.m_meshcache); | ||||
|     m_meshcache_valid = o.m_meshcache_valid; | ||||
|  | @ -413,7 +100,6 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o) | |||
|     m_pillars = o.m_pillars; | ||||
|     m_bridges = o.m_bridges; | ||||
|     m_crossbridges = o.m_crossbridges; | ||||
|     m_compact_bridges = o.m_compact_bridges; | ||||
|     m_pad = o.m_pad; | ||||
|     m_meshcache = o.m_meshcache; | ||||
|     m_meshcache_valid = o.m_meshcache_valid; | ||||
|  | @ -422,7 +108,19 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o) | |||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| const TriangleMesh &SupportTreeBuilder::merged_mesh() const | ||||
| void SupportTreeBuilder::add_pillar_base(long pid, double baseheight, double radius) | ||||
| { | ||||
|     std::lock_guard<Mutex> lk(m_mutex); | ||||
|     assert(pid >= 0 && size_t(pid) < m_pillars.size()); | ||||
|     Pillar& pll = m_pillars[size_t(pid)]; | ||||
|     m_pedestals.emplace_back(pll.endpt, std::min(baseheight, pll.height), | ||||
|                              std::max(radius, pll.r), pll.r); | ||||
| 
 | ||||
|     m_pedestals.back().id = m_pedestals.size() - 1; | ||||
|     m_meshcache_valid = false; | ||||
| } | ||||
| 
 | ||||
| const TriangleMesh &SupportTreeBuilder::merged_mesh(size_t steps) const | ||||
| { | ||||
|     if (m_meshcache_valid) return m_meshcache; | ||||
|      | ||||
|  | @ -430,35 +128,44 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh() const | |||
|      | ||||
|     for (auto &head : m_heads) { | ||||
|         if (ctl().stopcondition()) break; | ||||
|         if (head.is_valid()) merged.merge(head.mesh); | ||||
|         if (head.is_valid()) merged.merge(get_mesh(head, steps)); | ||||
|     } | ||||
|      | ||||
|     for (auto &stick : m_pillars) { | ||||
|     for (auto &pill : m_pillars) { | ||||
|         if (ctl().stopcondition()) break; | ||||
|         merged.merge(stick.mesh); | ||||
|         merged.merge(stick.base); | ||||
|         merged.merge(get_mesh(pill, steps)); | ||||
|     } | ||||
| 
 | ||||
|     for (auto &pedest : m_pedestals) { | ||||
|         if (ctl().stopcondition()) break; | ||||
|         merged.merge(get_mesh(pedest, steps)); | ||||
|     } | ||||
|      | ||||
|     for (auto &j : m_junctions) { | ||||
|         if (ctl().stopcondition()) break; | ||||
|         merged.merge(j.mesh); | ||||
|         merged.merge(get_mesh(j, steps)); | ||||
|     } | ||||
|      | ||||
|     for (auto &cb : m_compact_bridges) { | ||||
|         if (ctl().stopcondition()) break; | ||||
|         merged.merge(cb.mesh); | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     for (auto &bs : m_bridges) { | ||||
|         if (ctl().stopcondition()) break; | ||||
|         merged.merge(bs.mesh); | ||||
|         merged.merge(get_mesh(bs, steps)); | ||||
|     } | ||||
|      | ||||
|     for (auto &bs : m_crossbridges) { | ||||
|         if (ctl().stopcondition()) break; | ||||
|         merged.merge(bs.mesh); | ||||
|         merged.merge(get_mesh(bs, steps)); | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     for (auto &bs : m_diffbridges) { | ||||
|         if (ctl().stopcondition()) break; | ||||
|         merged.merge(get_mesh(bs, steps)); | ||||
|     } | ||||
| 
 | ||||
|     for (auto &anch : m_anchors) { | ||||
|         if (ctl().stopcondition()) break; | ||||
|         merged.merge(get_mesh(anch, steps)); | ||||
|     } | ||||
| 
 | ||||
|     if (ctl().stopcondition()) { | ||||
|         // In case of failure we have to return an empty mesh
 | ||||
|         m_meshcache = TriangleMesh(); | ||||
|  | @ -499,7 +206,6 @@ const TriangleMesh &SupportTreeBuilder::merge_and_cleanup() | |||
|     m_pillars = {}; | ||||
|     m_junctions = {}; | ||||
|     m_bridges = {}; | ||||
|     m_compact_bridges = {}; | ||||
|      | ||||
|     return ret; | ||||
| } | ||||
|  | @ -514,11 +220,4 @@ const TriangleMesh &SupportTreeBuilder::retrieve_mesh(MeshType meshtype) const | |||
|     return m_meshcache; | ||||
| } | ||||
| 
 | ||||
| bool SupportTreeBuilder::build(const SupportableMesh &sm) | ||||
| { | ||||
|     ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm; | ||||
|     return SupportTreeBuildsteps::execute(*this, sm); | ||||
| } | ||||
| 
 | ||||
| } | ||||
| } | ||||
| }} // namespace Slic3r::sla
 | ||||
|  |  | |||
|  | @ -2,7 +2,6 @@ | |||
| #define SLA_SUPPORTTREEBUILDER_HPP | ||||
| 
 | ||||
| #include <libslic3r/SLA/Concurrency.hpp> | ||||
| #include <libslic3r/SLA/Common.hpp> | ||||
| #include <libslic3r/SLA/SupportTree.hpp> | ||||
| #include <libslic3r/SLA/Contour3D.hpp> | ||||
| #include <libslic3r/SLA/Pad.hpp> | ||||
|  | @ -50,13 +49,6 @@ namespace sla { | |||
|  * nearby pillar. | ||||
|  */ | ||||
| 
 | ||||
| using Coordf = double; | ||||
| using Portion = std::tuple<double, double>; | ||||
| 
 | ||||
| inline Portion make_portion(double a, double b) { | ||||
|     return std::make_tuple(a, b); | ||||
| } | ||||
| 
 | ||||
| template<class Vec> double distance(const Vec& p) { | ||||
|     return std::sqrt(p.transpose() * p); | ||||
| } | ||||
|  | @ -66,33 +58,25 @@ template<class Vec> double distance(const Vec& pp1, const Vec& pp2) { | |||
|     return distance(p); | ||||
| } | ||||
| 
 | ||||
| Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI), | ||||
|                  double fa=(2*PI/360)); | ||||
| const Vec3d DOWN = {0.0, 0.0, -1.0}; | ||||
| 
 | ||||
| // Down facing cylinder in Z direction with arguments:
 | ||||
| // r: radius
 | ||||
| // h: Height
 | ||||
| // ssteps: how many edges will create the base circle
 | ||||
| // sp: starting point
 | ||||
| Contour3D cylinder(double r, double h, size_t ssteps = 45, const Vec3d &sp = {0,0,0}); | ||||
| struct SupportTreeNode | ||||
| { | ||||
|     static const constexpr long ID_UNSET = -1; | ||||
| 
 | ||||
| const constexpr long ID_UNSET = -1; | ||||
|     long id = ID_UNSET; // For identification withing a tree.
 | ||||
| }; | ||||
| 
 | ||||
| struct Head { | ||||
|     Contour3D mesh; | ||||
|      | ||||
|     size_t steps = 45; | ||||
|     Vec3d dir = {0, 0, -1}; | ||||
|     Vec3d tr = {0, 0, 0}; | ||||
| // A pinhead originating from a support point
 | ||||
| struct Head: public SupportTreeNode { | ||||
|     Vec3d dir = DOWN; | ||||
|     Vec3d pos = {0, 0, 0}; | ||||
|      | ||||
|     double r_back_mm = 1; | ||||
|     double r_pin_mm = 0.5; | ||||
|     double width_mm = 2; | ||||
|     double penetration_mm = 0.5; | ||||
|      | ||||
|     // For identification purposes. This will be used as the index into the
 | ||||
|     // container holding the head structures. See SLASupportTree::Impl
 | ||||
|     long id = ID_UNSET; | ||||
| 
 | ||||
|      | ||||
|     // If there is a pillar connecting to this head, then the id will be set.
 | ||||
|     long pillar_id = ID_UNSET; | ||||
|  | @ -106,31 +90,23 @@ struct Head { | |||
|          double r_small_mm, | ||||
|          double length_mm, | ||||
|          double penetration, | ||||
|          const Vec3d &direction = {0, 0, -1},  // direction (normal to the dull end)
 | ||||
|          const Vec3d &offset = {0, 0, 0},      // displacement
 | ||||
|          const size_t circlesteps = 45); | ||||
|      | ||||
|     void transform() | ||||
|          const Vec3d &direction = DOWN,  // direction (normal to the dull end)
 | ||||
|          const Vec3d &offset = {0, 0, 0}      // displacement
 | ||||
|          ); | ||||
| 
 | ||||
|     inline double real_width() const | ||||
|     { | ||||
|         using Quaternion = Eigen::Quaternion<double>; | ||||
|          | ||||
|         // We rotate the head to the specified direction The head's pointing
 | ||||
|         // side is facing upwards so this means that it would hold a support
 | ||||
|         // point with a normal pointing straight down. This is the reason of
 | ||||
|         // the -1 z coordinate
 | ||||
|         auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, dir); | ||||
|          | ||||
|         for(auto& p : mesh.points) p = quatern * p + tr; | ||||
|         return 2 * r_pin_mm + width_mm + 2 * r_back_mm ; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     inline double fullwidth() const | ||||
|     { | ||||
|         return 2 * r_pin_mm + width_mm + 2*r_back_mm - penetration_mm; | ||||
|         return real_width() - penetration_mm; | ||||
|     } | ||||
|      | ||||
|     inline Vec3d junction_point() const | ||||
|     { | ||||
|         return tr + ( 2 * r_pin_mm + width_mm + r_back_mm - penetration_mm)*dir; | ||||
|         return pos + (fullwidth() - r_back_mm) * dir; | ||||
|     } | ||||
|      | ||||
|     inline double request_pillar_radius(double radius) const | ||||
|  | @ -140,31 +116,17 @@ struct Head { | |||
|     } | ||||
| }; | ||||
| 
 | ||||
| struct Junction { | ||||
|     Contour3D mesh; | ||||
| // A junction connecting bridges and pillars
 | ||||
| struct Junction: public SupportTreeNode { | ||||
|     double r = 1; | ||||
|     size_t steps = 45; | ||||
|     Vec3d pos; | ||||
|      | ||||
|     long id = ID_UNSET; | ||||
|      | ||||
|     Junction(const Vec3d& tr, double r_mm, size_t stepnum = 45): | ||||
|         r(r_mm), steps(stepnum), pos(tr) | ||||
|     { | ||||
|         mesh = sphere(r_mm, make_portion(0, PI), 2*PI/steps); | ||||
|         for(auto& p : mesh.points) p += tr; | ||||
|     } | ||||
| 
 | ||||
|     Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {} | ||||
| }; | ||||
| 
 | ||||
| struct Pillar { | ||||
|     Contour3D mesh; | ||||
|     Contour3D base; | ||||
|     double r = 1; | ||||
|     size_t steps = 0; | ||||
| struct Pillar: public SupportTreeNode { | ||||
|     double height, r; | ||||
|     Vec3d endpt; | ||||
|     double height = 0; | ||||
|      | ||||
|     long id = ID_UNSET; | ||||
|      | ||||
|     // If the pillar connects to a head, this is the id of that head
 | ||||
|     bool starts_from_head = true; // Could start from a junction as well
 | ||||
|  | @ -175,54 +137,52 @@ struct Pillar { | |||
|      | ||||
|     // How many pillars are cascaded with this one
 | ||||
|     unsigned links = 0; | ||||
|      | ||||
|     Pillar(const Vec3d& jp, const Vec3d& endp, | ||||
|            double radius = 1, size_t st = 45); | ||||
|      | ||||
|     Pillar(const Junction &junc, const Vec3d &endp) | ||||
|         : Pillar(junc.pos, endp, junc.r, junc.steps) | ||||
|     {} | ||||
|      | ||||
|     Pillar(const Head &head, const Vec3d &endp, double radius = 1) | ||||
|         : Pillar(head.junction_point(), endp, | ||||
|                  head.request_pillar_radius(radius), head.steps) | ||||
|     {} | ||||
|      | ||||
|     inline Vec3d startpoint() const | ||||
| 
 | ||||
|     Pillar(const Vec3d &endp, double h, double radius = 1.): | ||||
|         height{h}, r(radius), endpt(endp), starts_from_head(false) {} | ||||
| 
 | ||||
|     Vec3d startpoint() const | ||||
|     { | ||||
|         return {endpt(X), endpt(Y), endpt(Z) + height}; | ||||
|         return {endpt.x(), endpt.y(), endpt.z() + height}; | ||||
|     } | ||||
|      | ||||
|     inline const Vec3d& endpoint() const { return endpt; } | ||||
|      | ||||
|     Pillar& add_base(double baseheight = 3, double radius = 2); | ||||
|     const Vec3d& endpoint() const { return endpt; } | ||||
| }; | ||||
| 
 | ||||
| // A base for pillars or bridges that end on the ground
 | ||||
| struct Pedestal: public SupportTreeNode { | ||||
|     Vec3d pos; | ||||
|     double height, r_bottom, r_top; | ||||
| 
 | ||||
|     Pedestal(const Vec3d &p, double h, double rbottom, double rtop) | ||||
|         : pos{p}, height{h}, r_bottom{rbottom}, r_top{rtop} | ||||
|     {} | ||||
| }; | ||||
| 
 | ||||
| // This is the thing that anchors a pillar or bridge to the model body.
 | ||||
| // It is actually a reverse pinhead.
 | ||||
| struct Anchor: public Head { using Head::Head; }; | ||||
| 
 | ||||
| // A Bridge between two pillars (with junction endpoints)
 | ||||
| struct Bridge { | ||||
|     Contour3D mesh; | ||||
| struct Bridge: public SupportTreeNode { | ||||
|     double r = 0.8; | ||||
|     long id = ID_UNSET; | ||||
|     Vec3d startp = Vec3d::Zero(), endp = Vec3d::Zero(); | ||||
|      | ||||
|     Bridge(const Vec3d &j1, | ||||
|            const Vec3d &j2, | ||||
|            double       r_mm  = 0.8, | ||||
|            size_t       steps = 45); | ||||
|            double       r_mm  = 0.8): r{r_mm}, startp{j1}, endp{j2} | ||||
|     {} | ||||
| 
 | ||||
|     double get_length() const { return (endp - startp).norm(); } | ||||
|     Vec3d  get_dir() const { return (endp - startp).normalized(); } | ||||
| }; | ||||
| 
 | ||||
| // A bridge that spans from model surface to model surface with small connecting
 | ||||
| // edges on the endpoints. Used for headless support points.
 | ||||
| struct CompactBridge { | ||||
|     Contour3D mesh; | ||||
|     long id = ID_UNSET; | ||||
|      | ||||
|     CompactBridge(const Vec3d& sp, | ||||
|                   const Vec3d& ep, | ||||
|                   const Vec3d& n, | ||||
|                   double r, | ||||
|                   bool endball = true, | ||||
|                   size_t steps = 45); | ||||
| struct DiffBridge: public Bridge { | ||||
|     double end_r; | ||||
| 
 | ||||
|     DiffBridge(const Vec3d &p_s, const Vec3d &p_e, double r_s, double r_e) | ||||
|         : Bridge{p_s, p_e, r_s}, end_r{r_e} | ||||
|     {} | ||||
| }; | ||||
| 
 | ||||
| // A wrapper struct around the pad
 | ||||
|  | @ -258,13 +218,16 @@ struct Pad { | |||
| // merged mesh. It can be retrieved using a dedicated method (pad())
 | ||||
| class SupportTreeBuilder: public SupportTree { | ||||
|     // For heads it is beneficial to use the same IDs as for the support points.
 | ||||
|     std::vector<Head> m_heads; | ||||
|     std::vector<size_t> m_head_indices; | ||||
|     std::vector<Pillar> m_pillars; | ||||
|     std::vector<Junction> m_junctions; | ||||
|     std::vector<Bridge> m_bridges; | ||||
|     std::vector<Bridge> m_crossbridges; | ||||
|     std::vector<CompactBridge> m_compact_bridges;     | ||||
|     std::vector<Head>       m_heads; | ||||
|     std::vector<size_t>     m_head_indices; | ||||
|     std::vector<Pillar>     m_pillars; | ||||
|     std::vector<Junction>   m_junctions; | ||||
|     std::vector<Bridge>     m_bridges; | ||||
|     std::vector<Bridge>     m_crossbridges; | ||||
|     std::vector<DiffBridge> m_diffbridges; | ||||
|     std::vector<Pedestal>   m_pedestals; | ||||
|     std::vector<Anchor>     m_anchors; | ||||
| 
 | ||||
|     Pad m_pad; | ||||
|      | ||||
|     using Mutex = ccr::SpinningMutex; | ||||
|  | @ -274,8 +237,8 @@ class SupportTreeBuilder: public SupportTree { | |||
|     mutable bool m_meshcache_valid = false; | ||||
|     mutable double m_model_height = 0; // the full height of the model
 | ||||
|      | ||||
|     template<class...Args> | ||||
|     const Bridge& _add_bridge(std::vector<Bridge> &br, Args&&... args) | ||||
|     template<class BridgeT, class...Args> | ||||
|     const BridgeT& _add_bridge(std::vector<BridgeT> &br, Args&&... args) | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         br.emplace_back(std::forward<Args>(args)...); | ||||
|  | @ -306,7 +269,7 @@ public: | |||
|         return m_heads.back(); | ||||
|     } | ||||
|      | ||||
|     template<class...Args> long add_pillar(long headid, Args&&... args) | ||||
|     template<class...Args> long add_pillar(long headid, double length) | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         if (m_pillars.capacity() < m_heads.size()) | ||||
|  | @ -315,7 +278,9 @@ public: | |||
|         assert(headid >= 0 && size_t(headid) < m_head_indices.size()); | ||||
|         Head &head = m_heads[m_head_indices[size_t(headid)]]; | ||||
|          | ||||
|         m_pillars.emplace_back(head, std::forward<Args>(args)...); | ||||
|         Vec3d hjp = head.junction_point() - Vec3d{0, 0, length}; | ||||
|         m_pillars.emplace_back(hjp, length, head.r_back_mm); | ||||
| 
 | ||||
|         Pillar& pillar = m_pillars.back(); | ||||
|         pillar.id = long(m_pillars.size() - 1); | ||||
|         head.pillar_id = pillar.id; | ||||
|  | @ -326,11 +291,15 @@ public: | |||
|         return pillar.id; | ||||
|     } | ||||
|      | ||||
|     void add_pillar_base(long pid, double baseheight = 3, double radius = 2) | ||||
|     void add_pillar_base(long pid, double baseheight = 3, double radius = 2); | ||||
| 
 | ||||
|     template<class...Args> const Anchor& add_anchor(Args&&...args) | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         assert(pid >= 0 && size_t(pid) < m_pillars.size()); | ||||
|         m_pillars[size_t(pid)].add_base(baseheight, radius); | ||||
|         m_anchors.emplace_back(std::forward<Args>(args)...); | ||||
|         m_anchors.back().id = long(m_junctions.size() - 1); | ||||
|         m_meshcache_valid = false; | ||||
|         return m_anchors.back(); | ||||
|     } | ||||
|      | ||||
|     void increment_bridges(const Pillar& pillar) | ||||
|  | @ -371,17 +340,6 @@ public: | |||
|         return pillar.id; | ||||
|     } | ||||
|      | ||||
|     const Pillar& head_pillar(unsigned headid) const | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         assert(headid < m_head_indices.size()); | ||||
|          | ||||
|         const Head& h = m_heads[m_head_indices[headid]]; | ||||
|         assert(h.pillar_id >= 0 && h.pillar_id < long(m_pillars.size())); | ||||
|          | ||||
|         return m_pillars[size_t(h.pillar_id)]; | ||||
|     } | ||||
|      | ||||
|     template<class...Args> const Junction& add_junction(Args&&... args) | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|  | @ -391,18 +349,18 @@ public: | |||
|         return m_junctions.back(); | ||||
|     } | ||||
|      | ||||
|     const Bridge& add_bridge(const Vec3d &s, const Vec3d &e, double r, size_t n = 45) | ||||
|     const Bridge& add_bridge(const Vec3d &s, const Vec3d &e, double r) | ||||
|     { | ||||
|         return _add_bridge(m_bridges, s, e, r, n); | ||||
|         return _add_bridge(m_bridges, s, e, r); | ||||
|     } | ||||
|      | ||||
|     const Bridge& add_bridge(long headid, const Vec3d &endp, size_t s = 45) | ||||
|     const Bridge& add_bridge(long headid, const Vec3d &endp) | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         assert(headid >= 0 && size_t(headid) < m_head_indices.size()); | ||||
|          | ||||
|         Head &h = m_heads[m_head_indices[size_t(headid)]]; | ||||
|         m_bridges.emplace_back(h.junction_point(), endp, h.r_back_mm, s); | ||||
|         m_bridges.emplace_back(h.junction_point(), endp, h.r_back_mm); | ||||
|         m_bridges.back().id = long(m_bridges.size() - 1); | ||||
|          | ||||
|         h.bridge_id = m_bridges.back().id; | ||||
|  | @ -414,14 +372,10 @@ public: | |||
|     { | ||||
|         return _add_bridge(m_crossbridges, std::forward<Args>(args)...); | ||||
|     } | ||||
|      | ||||
|     template<class...Args> const CompactBridge& add_compact_bridge(Args&&...args) | ||||
| 
 | ||||
|     template<class...Args> const DiffBridge& add_diffbridge(Args&&... args) | ||||
|     { | ||||
|         std::lock_guard<Mutex> lk(m_mutex); | ||||
|         m_compact_bridges.emplace_back(std::forward<Args>(args)...); | ||||
|         m_compact_bridges.back().id = long(m_compact_bridges.size() - 1); | ||||
|         m_meshcache_valid = false; | ||||
|         return m_compact_bridges.back(); | ||||
|         return _add_bridge(m_diffbridges, std::forward<Args>(args)...); | ||||
|     } | ||||
|      | ||||
|     Head &head(unsigned id) | ||||
|  | @ -439,7 +393,7 @@ public: | |||
|     } | ||||
|      | ||||
|     inline const std::vector<Pillar> &pillars() const { return m_pillars; } | ||||
|     inline const std::vector<Head> &heads() const { return m_heads; } | ||||
|     inline const std::vector<Head>   &heads() const { return m_heads; } | ||||
|     inline const std::vector<Bridge> &bridges() const { return m_bridges; } | ||||
|     inline const std::vector<Bridge> &crossbridges() const { return m_crossbridges; } | ||||
|      | ||||
|  | @ -464,7 +418,7 @@ public: | |||
|     const Pad& pad() const { return m_pad; } | ||||
|      | ||||
|     // WITHOUT THE PAD!!!
 | ||||
|     const TriangleMesh &merged_mesh() const; | ||||
|     const TriangleMesh &merged_mesh(size_t steps = 45) const; | ||||
|      | ||||
|     // WITH THE PAD
 | ||||
|     double full_height() const; | ||||
|  | @ -488,8 +442,6 @@ public: | |||
|      | ||||
|     virtual const TriangleMesh &retrieve_mesh( | ||||
|         MeshType meshtype = MeshType::Support) const override; | ||||
| 
 | ||||
|     bool build(const SupportableMesh &supportable_mesh); | ||||
| }; | ||||
| 
 | ||||
| }} // namespace Slic3r::sla
 | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -5,6 +5,7 @@ | |||
| 
 | ||||
| #include <libslic3r/SLA/SupportTreeBuilder.hpp> | ||||
| #include <libslic3r/SLA/Clustering.hpp> | ||||
| #include <libslic3r/SLA/SpatIndex.hpp> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace sla { | ||||
|  | @ -16,9 +17,7 @@ enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers | |||
|     X, Y, Z | ||||
| }; | ||||
| 
 | ||||
| inline Vec2d to_vec2(const Vec3d& v3) { | ||||
|     return {v3(X), v3(Y)}; | ||||
| } | ||||
| inline Vec2d to_vec2(const Vec3d &v3) { return {v3(X), v3(Y)}; } | ||||
| 
 | ||||
| inline std::pair<double, double> dir_to_spheric(const Vec3d &n, double norm = 1.) | ||||
| { | ||||
|  | @ -46,55 +45,71 @@ inline Vec3d spheric_to_dir(const std::pair<double, double> &v) | |||
|     return spheric_to_dir(v.first, v.second); | ||||
| } | ||||
| 
 | ||||
| // This function returns the position of the centroid in the input 'clust'
 | ||||
| // vector of point indices.
 | ||||
| template<class DistFn> | ||||
| long cluster_centroid(const ClusterEl& clust, | ||||
|                       const std::function<Vec3d(size_t)> &pointfn, | ||||
|                       DistFn df) | ||||
| inline Vec3d spheric_to_dir(const std::array<double, 2> &v) | ||||
| { | ||||
|     switch(clust.size()) { | ||||
|     case 0: /* empty cluster */ return ID_UNSET; | ||||
|     case 1: /* only one element */ return 0; | ||||
|     case 2: /* if two elements, there is no center */ return 0; | ||||
|     default: ; | ||||
|     return spheric_to_dir(v[0], v[1]); | ||||
| } | ||||
| 
 | ||||
| // Give points on a 3D ring with given center, radius and orientation
 | ||||
| // method based on:
 | ||||
| // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
 | ||||
| template<size_t N> | ||||
| class PointRing { | ||||
|     std::array<double, N> m_phis; | ||||
| 
 | ||||
|     // Two vectors that will be perpendicular to each other and to the
 | ||||
|     // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a
 | ||||
|     // placeholder.
 | ||||
|     // a and b vectors are perpendicular to the ring direction and to each other.
 | ||||
|     // Together they define the plane where we have to iterate with the
 | ||||
|     // given angles in the 'm_phis' vector
 | ||||
|     Vec3d a = {0, 1, 0}, b; | ||||
|     double m_radius = 0.; | ||||
| 
 | ||||
|     static inline bool constexpr is_one(double val) | ||||
|     { | ||||
|         return std::abs(std::abs(val) - 1) < 1e-20; | ||||
|     } | ||||
| 
 | ||||
|     // The function works by calculating for each point the average distance
 | ||||
|     // from all the other points in the cluster. We create a selector bitmask of
 | ||||
|     // the same size as the cluster. The bitmask will have two true bits and
 | ||||
|     // false bits for the rest of items and we will loop through all the
 | ||||
|     // permutations of the bitmask (combinations of two points). Get the
 | ||||
|     // distance for the two points and add the distance to the averages.
 | ||||
|     // The point with the smallest average than wins.
 | ||||
| public: | ||||
| 
 | ||||
|     // The complexity should be O(n^2) but we will mostly apply this function
 | ||||
|     // for small clusters only (cca 3 elements)
 | ||||
|     PointRing(const Vec3d &n) | ||||
|     { | ||||
|         m_phis = linspace_array<N>(0., 2 * PI); | ||||
| 
 | ||||
|     std::vector<bool> sel(clust.size(), false);   // create full zero bitmask
 | ||||
|     std::fill(sel.end() - 2, sel.end(), true);    // insert the two ones
 | ||||
|     std::vector<double> avgs(clust.size(), 0.0);  // store the average distances
 | ||||
|         // We have to address the case when the direction vector v (same as
 | ||||
|         // dir) is coincident with one of the world axes. In this case two of
 | ||||
|         // its components will be completely zero and one is 1.0. Our method
 | ||||
|         // becomes dangerous here due to division with zero. Instead, vector
 | ||||
|         // 'a' can be an element-wise rotated version of 'v'
 | ||||
|         if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) { | ||||
|             a = {n(Z), n(X), n(Y)}; | ||||
|             b = {n(Y), n(Z), n(X)}; | ||||
|         } | ||||
|         else { | ||||
|             a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize(); | ||||
|             b = a.cross(n); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     do { | ||||
|         std::array<size_t, 2> idx; | ||||
|         for(size_t i = 0, j = 0; i < clust.size(); i++) if(sel[i]) idx[j++] = i; | ||||
|     Vec3d get(size_t idx, const Vec3d src, double r) const | ||||
|     { | ||||
|         double phi = m_phis[idx]; | ||||
|         double sinphi = std::sin(phi); | ||||
|         double cosphi = std::cos(phi); | ||||
| 
 | ||||
|         double d = df(pointfn(clust[idx[0]]), | ||||
|                       pointfn(clust[idx[1]])); | ||||
|         double rpscos = r * cosphi; | ||||
|         double rpssin = r * sinphi; | ||||
| 
 | ||||
|         // add the distance to the sums for both associated points
 | ||||
|         for(auto i : idx) avgs[i] += d; | ||||
|         // Point on the sphere
 | ||||
|         return {src(X) + rpscos * a(X) + rpssin * b(X), | ||||
|                 src(Y) + rpscos * a(Y) + rpssin * b(Y), | ||||
|                 src(Z) + rpscos * a(Z) + rpssin * b(Z)}; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
|         // now continue with the next permutation of the bitmask with two 1s
 | ||||
|     } while(std::next_permutation(sel.begin(), sel.end())); | ||||
| 
 | ||||
|     // Divide by point size in the cluster to get the average (may be redundant)
 | ||||
|     for(auto& a : avgs) a /= clust.size(); | ||||
| 
 | ||||
|     // get the lowest average distance and return the index
 | ||||
|     auto minit = std::min_element(avgs.begin(), avgs.end()); | ||||
|     return long(minit - avgs.begin()); | ||||
| } | ||||
| //IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan(""));
 | ||||
| //IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan(""));
 | ||||
| 
 | ||||
| inline Vec3d dirv(const Vec3d& startp, const Vec3d& endp) { | ||||
|     return (endp - startp).normalized(); | ||||
|  | @ -170,8 +185,8 @@ IntegerOnly<DoubleI> pairhash(I a, I b) | |||
| } | ||||
| 
 | ||||
| class SupportTreeBuildsteps { | ||||
|     const SupportConfig& m_cfg; | ||||
|     const EigenMesh3D& m_mesh; | ||||
|     const SupportTreeConfig& m_cfg; | ||||
|     const IndexedMesh& m_mesh; | ||||
|     const std::vector<SupportPoint>& m_support_pts; | ||||
| 
 | ||||
|     using PtIndices = std::vector<unsigned>; | ||||
|  | @ -180,7 +195,7 @@ class SupportTreeBuildsteps { | |||
|     PtIndices m_iheads_onmodel; | ||||
|     PtIndices m_iheadless;         // headless support points
 | ||||
|      | ||||
|     std::map<unsigned, EigenMesh3D::hit_result> m_head_to_ground_scans; | ||||
|     std::map<unsigned, IndexedMesh::hit_result> m_head_to_ground_scans; | ||||
| 
 | ||||
|     // normals for support points from model faces.
 | ||||
|     PointSet  m_support_nmls; | ||||
|  | @ -206,7 +221,7 @@ class SupportTreeBuildsteps { | |||
|     // When bridging heads to pillars... TODO: find a cleaner solution
 | ||||
|     ccr::BlockingMutex m_bridge_mutex; | ||||
| 
 | ||||
|     inline EigenMesh3D::hit_result ray_mesh_intersect(const Vec3d& s,  | ||||
|     inline IndexedMesh::hit_result ray_mesh_intersect(const Vec3d& s,  | ||||
|                                                       const Vec3d& dir) | ||||
|     { | ||||
|         return m_mesh.query_ray_hit(s, dir); | ||||
|  | @ -223,16 +238,24 @@ class SupportTreeBuildsteps { | |||
|     // point was inside the model, an "invalid" hit_result will be returned
 | ||||
|     // with a zero distance value instead of a NAN. This way the result can
 | ||||
|     // be used safely for comparison with other distances.
 | ||||
|     EigenMesh3D::hit_result pinhead_mesh_intersect( | ||||
|     IndexedMesh::hit_result pinhead_mesh_intersect( | ||||
|         const Vec3d& s, | ||||
|         const Vec3d& dir, | ||||
|         double r_pin, | ||||
|         double r_back, | ||||
|         double width); | ||||
|      | ||||
|     template<class...Args> | ||||
|     inline double pinhead_mesh_distance(Args&&...args) { | ||||
|         return pinhead_mesh_intersect(std::forward<Args>(args)...).distance(); | ||||
|         double width, | ||||
|         double safety_d); | ||||
| 
 | ||||
|     IndexedMesh::hit_result pinhead_mesh_intersect( | ||||
|         const Vec3d& s, | ||||
|         const Vec3d& dir, | ||||
|         double r_pin, | ||||
|         double r_back, | ||||
|         double width) | ||||
|     { | ||||
|         return pinhead_mesh_intersect(s, dir, r_pin, r_back, width, | ||||
|                                       r_back * m_cfg.safety_distance_mm / | ||||
|                                           m_cfg.head_back_radius_mm); | ||||
|     } | ||||
| 
 | ||||
|     // Checking bridge (pillar and stick as well) intersection with the model.
 | ||||
|  | @ -243,11 +266,21 @@ class SupportTreeBuildsteps { | |||
|     // point was inside the model, an "invalid" hit_result will be returned
 | ||||
|     // with a zero distance value instead of a NAN. This way the result can
 | ||||
|     // be used safely for comparison with other distances.
 | ||||
|     EigenMesh3D::hit_result bridge_mesh_intersect( | ||||
|     IndexedMesh::hit_result bridge_mesh_intersect( | ||||
|         const Vec3d& s, | ||||
|         const Vec3d& dir, | ||||
|         double r, | ||||
|         bool ins_check = false); | ||||
|         double safety_d); | ||||
| 
 | ||||
|     IndexedMesh::hit_result bridge_mesh_intersect( | ||||
|         const Vec3d& s, | ||||
|         const Vec3d& dir, | ||||
|         double r) | ||||
|     { | ||||
|         return bridge_mesh_intersect(s, dir, r, | ||||
|                                      r * m_cfg.safety_distance_mm / | ||||
|                                          m_cfg.head_back_radius_mm); | ||||
|     } | ||||
|      | ||||
|     template<class...Args> | ||||
|     inline double bridge_mesh_distance(Args&&...args) { | ||||
|  | @ -268,20 +301,29 @@ class SupportTreeBuildsteps { | |||
|     inline bool connect_to_ground(Head& head); | ||||
|      | ||||
|     bool connect_to_model_body(Head &head); | ||||
|      | ||||
|     bool search_pillar_and_connect(const Head& head); | ||||
| 
 | ||||
|     bool search_pillar_and_connect(const Head& source); | ||||
|      | ||||
|     // This is a proxy function for pillar creation which will mind the gap
 | ||||
|     // between the pad and the model bottom in zero elevation mode.
 | ||||
|     // jp is the starting junction point which needs to be routed down.
 | ||||
|     // sourcedir is the allowed direction of an optional bridge between the
 | ||||
|     // jp junction and the final pillar.
 | ||||
|     void create_ground_pillar(const Vec3d &jp, | ||||
|     bool create_ground_pillar(const Vec3d &jp, | ||||
|                               const Vec3d &sourcedir, | ||||
|                               double       radius, | ||||
|                               long         head_id = ID_UNSET); | ||||
|      | ||||
|      | ||||
|                               long         head_id = SupportTreeNode::ID_UNSET); | ||||
| 
 | ||||
|     void add_pillar_base(long pid) | ||||
|     { | ||||
|         m_builder.add_pillar_base(pid, m_cfg.base_height_mm, m_cfg.base_radius_mm); | ||||
|     } | ||||
| 
 | ||||
|     std::optional<DiffBridge> search_widening_path(const Vec3d &jp, | ||||
|                                                    const Vec3d &dir, | ||||
|                                                    double       radius, | ||||
|                                                    double       new_radius); | ||||
| 
 | ||||
| public: | ||||
|     SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm); | ||||
| 
 | ||||
|  | @ -324,11 +366,6 @@ public: | |||
| 
 | ||||
|     void interconnect_pillars(); | ||||
| 
 | ||||
|     // Step: process the support points where there is not enough space for a
 | ||||
|     // full pinhead. In this case we will use a rounded sphere as a touching
 | ||||
|     // point and use a thinner bridge (let's call it a stick).
 | ||||
|     void routing_headless (); | ||||
| 
 | ||||
|     inline void merge_result() { m_builder.merged_mesh(); } | ||||
| 
 | ||||
|     static bool execute(SupportTreeBuilder & builder, const SupportableMesh &sm); | ||||
|  |  | |||
							
								
								
									
										266
									
								
								src/libslic3r/SLA/SupportTreeMesher.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										266
									
								
								src/libslic3r/SLA/SupportTreeMesher.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,266 @@ | |||
| #include "SupportTreeMesher.hpp" | ||||
| 
 | ||||
| namespace Slic3r { namespace sla { | ||||
| 
 | ||||
| Contour3D sphere(double rho, Portion portion, double fa) { | ||||
| 
 | ||||
|     Contour3D ret; | ||||
| 
 | ||||
|     // prohibit close to zero radius
 | ||||
|     if(rho <= 1e-6 && rho >= -1e-6) return ret; | ||||
| 
 | ||||
|     auto& vertices = ret.points; | ||||
|     auto& facets = ret.faces3; | ||||
| 
 | ||||
|     // Algorithm:
 | ||||
|     // Add points one-by-one to the sphere grid and form facets using relative
 | ||||
|     // coordinates. Sphere is composed effectively of a mesh of stacked circles.
 | ||||
| 
 | ||||
|     // adjust via rounding to get an even multiple for any provided angle.
 | ||||
|     double angle = (2*PI / floor(2*PI / fa)); | ||||
| 
 | ||||
|     // Ring to be scaled to generate the steps of the sphere
 | ||||
|     std::vector<double> ring; | ||||
| 
 | ||||
|     for (double i = 0; i < 2*PI; i+=angle) ring.emplace_back(i); | ||||
| 
 | ||||
|     const auto sbegin = size_t(2*std::get<0>(portion)/angle); | ||||
|     const auto send = size_t(2*std::get<1>(portion)/angle); | ||||
| 
 | ||||
|     const size_t steps = ring.size(); | ||||
|     const double increment = 1.0 / double(steps); | ||||
| 
 | ||||
|     // special case: first ring connects to 0,0,0
 | ||||
|     // insert and form facets.
 | ||||
|     if(sbegin == 0) | ||||
|         vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*sbegin*2.0*rho)); | ||||
| 
 | ||||
|     auto id = coord_t(vertices.size()); | ||||
|     for (size_t i = 0; i < ring.size(); i++) { | ||||
|         // Fixed scaling
 | ||||
|         const double z = -rho + increment*rho*2.0 * (sbegin + 1.0); | ||||
|         // radius of the circle for this step.
 | ||||
|         const double r = std::sqrt(std::abs(rho*rho - z*z)); | ||||
|         Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); | ||||
|         vertices.emplace_back(Vec3d(b(0), b(1), z)); | ||||
| 
 | ||||
|         if (sbegin == 0) | ||||
|             (i == 0) ? facets.emplace_back(coord_t(ring.size()), 0, 1) : | ||||
|                        facets.emplace_back(id - 1, 0, id); | ||||
|         ++id; | ||||
|     } | ||||
| 
 | ||||
|     // General case: insert and form facets for each step,
 | ||||
|     // joining it to the ring below it.
 | ||||
|     for (size_t s = sbegin + 2; s < send - 1; s++) { | ||||
|         const double z = -rho + increment*double(s*2.0*rho); | ||||
|         const double r = std::sqrt(std::abs(rho*rho - z*z)); | ||||
| 
 | ||||
|         for (size_t i = 0; i < ring.size(); i++) { | ||||
|             Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); | ||||
|             vertices.emplace_back(Vec3d(b(0), b(1), z)); | ||||
|             auto id_ringsize = coord_t(id - int(ring.size())); | ||||
|             if (i == 0) { | ||||
|                 // wrap around
 | ||||
|                 facets.emplace_back(id - 1, id, id + coord_t(ring.size() - 1) ); | ||||
|                 facets.emplace_back(id - 1, id_ringsize, id); | ||||
|             } else { | ||||
|                 facets.emplace_back(id_ringsize - 1, id_ringsize, id); | ||||
|                 facets.emplace_back(id - 1, id_ringsize - 1, id); | ||||
|             } | ||||
|             id++; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // special case: last ring connects to 0,0,rho*2.0
 | ||||
|     // only form facets.
 | ||||
|     if(send >= size_t(2*PI / angle)) { | ||||
|         vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*send*2.0*rho)); | ||||
|         for (size_t i = 0; i < ring.size(); i++) { | ||||
|             auto id_ringsize = coord_t(id - int(ring.size())); | ||||
|             if (i == 0) { | ||||
|                 // third vertex is on the other side of the ring.
 | ||||
|                 facets.emplace_back(id - 1, id_ringsize, id); | ||||
|             } else { | ||||
|                 auto ci = coord_t(id_ringsize + coord_t(i)); | ||||
|                 facets.emplace_back(ci - 1, ci, id); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     id++; | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp) | ||||
| { | ||||
|     assert(ssteps > 0); | ||||
| 
 | ||||
|     Contour3D ret; | ||||
| 
 | ||||
|     auto steps = int(ssteps); | ||||
|     auto& points = ret.points; | ||||
|     auto& indices = ret.faces3; | ||||
|     points.reserve(2*ssteps); | ||||
|     double a = 2*PI/steps; | ||||
| 
 | ||||
|     Vec3d jp = sp; | ||||
|     Vec3d endp = {sp(X), sp(Y), sp(Z) + h}; | ||||
| 
 | ||||
|     // Upper circle points
 | ||||
|     for(int i = 0; i < steps; ++i) { | ||||
|         double phi = i*a; | ||||
|         double ex = endp(X) + r*std::cos(phi); | ||||
|         double ey = endp(Y) + r*std::sin(phi); | ||||
|         points.emplace_back(ex, ey, endp(Z)); | ||||
|     } | ||||
| 
 | ||||
|     // Lower circle points
 | ||||
|     for(int i = 0; i < steps; ++i) { | ||||
|         double phi = i*a; | ||||
|         double x = jp(X) + r*std::cos(phi); | ||||
|         double y = jp(Y) + r*std::sin(phi); | ||||
|         points.emplace_back(x, y, jp(Z)); | ||||
|     } | ||||
| 
 | ||||
|     // Now create long triangles connecting upper and lower circles
 | ||||
|     indices.reserve(2*ssteps); | ||||
|     auto offs = steps; | ||||
|     for(int i = 0; i < steps - 1; ++i) { | ||||
|         indices.emplace_back(i, i + offs, offs + i + 1); | ||||
|         indices.emplace_back(i, offs + i + 1, i + 1); | ||||
|     } | ||||
| 
 | ||||
|     // Last triangle connecting the first and last vertices
 | ||||
|     auto last = steps - 1; | ||||
|     indices.emplace_back(0, last, offs); | ||||
|     indices.emplace_back(last, offs + last, offs); | ||||
| 
 | ||||
|     // According to the slicing algorithms, we need to aid them with generating
 | ||||
|     // a watertight body. So we create a triangle fan for the upper and lower
 | ||||
|     // ending of the cylinder to close the geometry.
 | ||||
|     points.emplace_back(jp); int ci = int(points.size() - 1); | ||||
|     for(int i = 0; i < steps - 1; ++i) | ||||
|         indices.emplace_back(i + offs + 1, i + offs, ci); | ||||
| 
 | ||||
|     indices.emplace_back(offs, steps + offs - 1, ci); | ||||
| 
 | ||||
|     points.emplace_back(endp); ci = int(points.size() - 1); | ||||
|     for(int i = 0; i < steps - 1; ++i) | ||||
|         indices.emplace_back(ci, i, i + 1); | ||||
| 
 | ||||
|     indices.emplace_back(steps - 1, 0, ci); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) | ||||
| { | ||||
|     assert(steps > 0); | ||||
|     assert(length >= 0.); | ||||
|     assert(r_back > 0.); | ||||
|     assert(r_pin > 0.); | ||||
| 
 | ||||
|     Contour3D mesh; | ||||
| 
 | ||||
|     // We create two spheres which will be connected with a robe that fits
 | ||||
|     // both circles perfectly.
 | ||||
| 
 | ||||
|     // Set up the model detail level
 | ||||
|     const double detail = 2 * PI / steps; | ||||
| 
 | ||||
|     // We don't generate whole circles. Instead, we generate only the
 | ||||
|     // portions which are visible (not covered by the robe) To know the
 | ||||
|     // exact portion of the bottom and top circles we need to use some
 | ||||
|     // rules of tangent circles from which we can derive (using simple
 | ||||
|     // triangles the following relations:
 | ||||
| 
 | ||||
|     // The height of the whole mesh
 | ||||
|     const double h   = r_back + r_pin + length; | ||||
|     double       phi = PI / 2. - std::acos((r_back - r_pin) / h); | ||||
| 
 | ||||
|     // To generate a whole circle we would pass a portion of (0, Pi)
 | ||||
|     // To generate only a half horizontal circle we can pass (0, Pi/2)
 | ||||
|     // The calculated phi is an offset to the half circles needed to smooth
 | ||||
|     // the transition from the circle to the robe geometry
 | ||||
| 
 | ||||
|     auto &&s1 = sphere(r_back, make_portion(0, PI / 2 + phi), detail); | ||||
|     auto &&s2 = sphere(r_pin, make_portion(PI / 2 + phi, PI), detail); | ||||
| 
 | ||||
|     for (auto &p : s2.points) p.z() += h; | ||||
| 
 | ||||
|     mesh.merge(s1); | ||||
|     mesh.merge(s2); | ||||
| 
 | ||||
|     for (size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); | ||||
|          idx1 < s1.points.size() - 1; idx1++, idx2++) { | ||||
|         coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2); | ||||
|         coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1; | ||||
| 
 | ||||
|         mesh.faces3.emplace_back(i1s1, i2s1, i2s2); | ||||
|         mesh.faces3.emplace_back(i1s1, i2s2, i1s2); | ||||
|     } | ||||
| 
 | ||||
|     auto i1s1 = coord_t(s1.points.size()) - coord_t(steps); | ||||
|     auto i2s1 = coord_t(s1.points.size()) - 1; | ||||
|     auto i1s2 = coord_t(s1.points.size()); | ||||
|     auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1; | ||||
| 
 | ||||
|     mesh.faces3.emplace_back(i2s2, i2s1, i1s1); | ||||
|     mesh.faces3.emplace_back(i1s2, i2s2, i1s1); | ||||
| 
 | ||||
|     return mesh; | ||||
| } | ||||
| 
 | ||||
| Contour3D halfcone(double       baseheight, | ||||
|                    double       r_bottom, | ||||
|                    double       r_top, | ||||
|                    const Vec3d &pos, | ||||
|                    size_t       steps) | ||||
| { | ||||
|     assert(steps > 0); | ||||
| 
 | ||||
|     if (baseheight <= 0 || steps <= 0) return {}; | ||||
| 
 | ||||
|     Contour3D base; | ||||
| 
 | ||||
|     double a    = 2 * PI / steps; | ||||
|     auto   last = int(steps - 1); | ||||
|     Vec3d  ep{pos.x(), pos.y(), pos.z() + baseheight}; | ||||
|     for (size_t i = 0; i < steps; ++i) { | ||||
|         double phi = i * a; | ||||
|         double x   = pos.x() + r_top * std::cos(phi); | ||||
|         double y   = pos.y() + r_top * std::sin(phi); | ||||
|         base.points.emplace_back(x, y, ep.z()); | ||||
|     } | ||||
| 
 | ||||
|     for (size_t i = 0; i < steps; ++i) { | ||||
|         double phi = i * a; | ||||
|         double x   = pos.x() + r_bottom * std::cos(phi); | ||||
|         double y   = pos.y() + r_bottom * std::sin(phi); | ||||
|         base.points.emplace_back(x, y, pos.z()); | ||||
|     } | ||||
| 
 | ||||
|     base.points.emplace_back(pos); | ||||
|     base.points.emplace_back(ep); | ||||
| 
 | ||||
|     auto &indices = base.faces3; | ||||
|     auto  hcenter = int(base.points.size() - 1); | ||||
|     auto  lcenter = int(base.points.size() - 2); | ||||
|     auto  offs    = int(steps); | ||||
|     for (int i = 0; i < last; ++i) { | ||||
|         indices.emplace_back(i, i + offs, offs + i + 1); | ||||
|         indices.emplace_back(i, offs + i + 1, i + 1); | ||||
|         indices.emplace_back(i, i + 1, hcenter); | ||||
|         indices.emplace_back(lcenter, offs + i + 1, offs + i); | ||||
|     } | ||||
| 
 | ||||
|     indices.emplace_back(0, last, offs); | ||||
|     indices.emplace_back(last, offs + last, offs); | ||||
|     indices.emplace_back(hcenter, last, 0); | ||||
|     indices.emplace_back(offs, offs + last, lcenter); | ||||
| 
 | ||||
|     return base; | ||||
| } | ||||
| 
 | ||||
| }} // namespace Slic3r::sla
 | ||||
							
								
								
									
										117
									
								
								src/libslic3r/SLA/SupportTreeMesher.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								src/libslic3r/SLA/SupportTreeMesher.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,117 @@ | |||
| #ifndef SUPPORTTREEMESHER_HPP | ||||
| #define SUPPORTTREEMESHER_HPP | ||||
| 
 | ||||
| #include "libslic3r/Point.hpp" | ||||
| 
 | ||||
| #include "libslic3r/SLA/SupportTreeBuilder.hpp" | ||||
| #include "libslic3r/SLA/Contour3D.hpp" | ||||
| 
 | ||||
| namespace Slic3r { namespace sla { | ||||
| 
 | ||||
| using Portion = std::tuple<double, double>; | ||||
| 
 | ||||
| inline Portion make_portion(double a, double b) | ||||
| { | ||||
|     return std::make_tuple(a, b); | ||||
| } | ||||
| 
 | ||||
| Contour3D sphere(double  rho, | ||||
|                  Portion portion = make_portion(0., 2. * PI), | ||||
|                  double  fa      = (2. * PI / 360.)); | ||||
| 
 | ||||
| // Down facing cylinder in Z direction with arguments:
 | ||||
| // r: radius
 | ||||
| // h: Height
 | ||||
| // ssteps: how many edges will create the base circle
 | ||||
| // sp: starting point
 | ||||
| Contour3D cylinder(double       r, | ||||
|                    double       h, | ||||
|                    size_t       steps = 45, | ||||
|                    const Vec3d &sp    = Vec3d::Zero()); | ||||
| 
 | ||||
| Contour3D pinhead(double r_pin, double r_back, double length, size_t steps = 45); | ||||
| 
 | ||||
| Contour3D halfcone(double       baseheight, | ||||
|                    double       r_bottom, | ||||
|                    double       r_top, | ||||
|                    const Vec3d &pt    = Vec3d::Zero(), | ||||
|                    size_t       steps = 45); | ||||
| 
 | ||||
| inline Contour3D get_mesh(const Head &h, size_t steps) | ||||
| { | ||||
|     Contour3D mesh = pinhead(h.r_pin_mm, h.r_back_mm, h.width_mm, steps); | ||||
| 
 | ||||
|     for(auto& p : mesh.points) p.z() -= (h.fullwidth() - h.r_back_mm); | ||||
| 
 | ||||
|     using Quaternion = Eigen::Quaternion<double>; | ||||
| 
 | ||||
|     // We rotate the head to the specified direction. The head's pointing
 | ||||
|     // side is facing upwards so this means that it would hold a support
 | ||||
|     // point with a normal pointing straight down. This is the reason of
 | ||||
|     // the -1 z coordinate
 | ||||
|     auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, h.dir); | ||||
| 
 | ||||
|     for(auto& p : mesh.points) p = quatern * p + h.pos; | ||||
| 
 | ||||
|     return mesh; | ||||
| } | ||||
| 
 | ||||
| inline Contour3D get_mesh(const Pillar &p, size_t steps) | ||||
| { | ||||
|     if(p.height > EPSILON) { // Endpoint is below the starting point
 | ||||
|         // We just create a bridge geometry with the pillar parameters and
 | ||||
|         // move the data.
 | ||||
|         return cylinder(p.r, p.height, steps, p.endpoint()); | ||||
|     } | ||||
| 
 | ||||
|     return {}; | ||||
| } | ||||
| 
 | ||||
| inline Contour3D get_mesh(const Pedestal &p, size_t steps) | ||||
| { | ||||
|     return halfcone(p.height, p.r_bottom, p.r_top, p.pos, steps); | ||||
| } | ||||
| 
 | ||||
| inline Contour3D get_mesh(const Junction &j, size_t steps) | ||||
| { | ||||
|     Contour3D mesh = sphere(j.r, make_portion(0, PI), 2 *PI / steps); | ||||
|     for(auto& p : mesh.points) p += j.pos; | ||||
|     return mesh; | ||||
| } | ||||
| 
 | ||||
| inline Contour3D get_mesh(const Bridge &br, size_t steps) | ||||
| { | ||||
|     using Quaternion = Eigen::Quaternion<double>; | ||||
|     Vec3d v = (br.endp - br.startp); | ||||
|     Vec3d dir = v.normalized(); | ||||
|     double d = v.norm(); | ||||
| 
 | ||||
|     Contour3D mesh = cylinder(br.r, d, steps); | ||||
| 
 | ||||
|     auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir); | ||||
|     for(auto& p : mesh.points) p = quater * p + br.startp; | ||||
| 
 | ||||
|     return mesh; | ||||
| } | ||||
| 
 | ||||
| inline Contour3D get_mesh(const DiffBridge &br, size_t steps) | ||||
| { | ||||
|     double h = br.get_length(); | ||||
|     Contour3D mesh = halfcone(h, br.r, br.end_r, Vec3d::Zero(), steps); | ||||
| 
 | ||||
|     using Quaternion = Eigen::Quaternion<double>; | ||||
| 
 | ||||
|     // We rotate the head to the specified direction. The head's pointing
 | ||||
|     // side is facing upwards so this means that it would hold a support
 | ||||
|     // point with a normal pointing straight down. This is the reason of
 | ||||
|     // the -1 z coordinate
 | ||||
|     auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, 1}, br.get_dir()); | ||||
| 
 | ||||
|     for(auto& p : mesh.points) p = quatern * p + br.startp; | ||||
| 
 | ||||
|     return mesh; | ||||
| } | ||||
| 
 | ||||
| }} // namespace Slic3r::sla
 | ||||
| 
 | ||||
| #endif // SUPPORTTREEMESHER_HPP
 | ||||
|  | @ -35,13 +35,16 @@ bool is_zero_elevation(const SLAPrintObjectConfig &c) | |||
| } | ||||
| 
 | ||||
| // Compile the argument for support creation from the static print config.
 | ||||
| sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) | ||||
| sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c) | ||||
| { | ||||
|     sla::SupportConfig scfg; | ||||
|     sla::SupportTreeConfig scfg; | ||||
|      | ||||
|     scfg.enabled = c.supports_enable.getBool(); | ||||
|     scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat(); | ||||
|     scfg.head_back_radius_mm = 0.5*c.support_pillar_diameter.getFloat(); | ||||
|     double pillar_r = 0.5 * c.support_pillar_diameter.getFloat(); | ||||
|     scfg.head_back_radius_mm = pillar_r; | ||||
|     scfg.head_fallback_radius_mm = | ||||
|         0.01 * c.support_small_pillar_diameter_percent.getFloat() * pillar_r; | ||||
|     scfg.head_penetration_mm = c.support_head_penetration.getFloat(); | ||||
|     scfg.head_width_mm = c.support_head_width.getFloat(); | ||||
|     scfg.object_elevation_mm = is_zero_elevation(c) ? | ||||
|  | @ -616,7 +619,7 @@ std::string SLAPrint::validate() const | |||
|             return L("Cannot proceed without support points! " | ||||
|                      "Add support points or disable support generation."); | ||||
| 
 | ||||
|         sla::SupportConfig cfg = make_support_cfg(po->config()); | ||||
|         sla::SupportTreeConfig cfg = make_support_cfg(po->config()); | ||||
| 
 | ||||
|         double elv = cfg.object_elevation_mm; | ||||
|          | ||||
|  | @ -925,6 +928,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf | |||
|             || opt_key == "support_head_penetration" | ||||
|             || opt_key == "support_head_width" | ||||
|             || opt_key == "support_pillar_diameter" | ||||
|             || opt_key == "support_small_pillar_diameter_percent" | ||||
|             || opt_key == "support_max_bridges_on_pillar" | ||||
|             || opt_key == "support_pillar_connection_mode" | ||||
|             || opt_key == "support_buildplate_only" | ||||
|  |  | |||
|  | @ -544,7 +544,7 @@ private: | |||
| 
 | ||||
| bool is_zero_elevation(const SLAPrintObjectConfig &c); | ||||
| 
 | ||||
| sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c); | ||||
| sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c); | ||||
| 
 | ||||
| sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c); | ||||
| 
 | ||||
|  |  | |||
|  | @ -360,18 +360,6 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) | |||
|         // removed them on purpose. No calculation will be done.
 | ||||
|         po.m_supportdata->pts = po.transformed_support_points(); | ||||
|     } | ||||
| 
 | ||||
|     // If the zero elevation mode is engaged, we have to filter out all the
 | ||||
|     // points that are on the bottom of the object
 | ||||
|     if (is_zero_elevation(po.config())) { | ||||
|         double tolerance = po.config().pad_enable.getBool() ? | ||||
|                                po.m_config.pad_wall_thickness.getFloat() : | ||||
|                                po.m_config.support_base_height.getFloat(); | ||||
| 
 | ||||
|         remove_bottom_points(po.m_supportdata->pts, | ||||
|                              po.m_supportdata->emesh.ground_level(), | ||||
|                              tolerance); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void SLAPrint::Steps::support_tree(SLAPrintObject &po) | ||||
|  | @ -382,6 +370,13 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po) | |||
|      | ||||
|     if (pcfg.embed_object) | ||||
|         po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm); | ||||
| 
 | ||||
|     // If the zero elevation mode is engaged, we have to filter out all the
 | ||||
|     // points that are on the bottom of the object
 | ||||
|     if (is_zero_elevation(po.config())) { | ||||
|         remove_bottom_points(po.m_supportdata->pts, | ||||
|                              float(po.m_supportdata->emesh.ground_level() + EPSILON)); | ||||
|     } | ||||
|      | ||||
|     po.m_supportdata->cfg = make_support_cfg(po.m_config); | ||||
| //    po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
 | ||||
|  |  | |||
|  | @ -169,6 +169,8 @@ set(SLIC3R_GUI_SOURCES | |||
|     GUI/InstanceCheck.hpp | ||||
|     GUI/Search.cpp | ||||
|     GUI/Search.hpp | ||||
|     GUI/NotificationManager.cpp | ||||
|     GUI/NotificationManager.hpp | ||||
|     Utils/Http.cpp | ||||
|     Utils/Http.hpp | ||||
|     Utils/FixModelByWin10.cpp | ||||
|  |  | |||
|  | @ -91,15 +91,17 @@ void BackgroundSlicingProcess::process_fff() | |||
| { | ||||
| 	assert(m_print == m_fff_print); | ||||
|     m_print->process(); | ||||
| 	wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_slicing_completed_id)); | ||||
| 	wxCommandEvent evt(m_event_slicing_completed_id); | ||||
| 	evt.SetInt((int)(m_fff_print->step_state_with_timestamp(PrintStep::psBrim).timestamp)); | ||||
| 	wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone()); | ||||
| #if ENABLE_GCODE_VIEWER | ||||
| 	m_fff_print->export_gcode(m_temp_output_path, m_gcode_result, m_thumbnail_cb); | ||||
| #else | ||||
|     m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_cb); | ||||
| 	m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_cb); | ||||
| #endif // ENABLE_GCODE_VIEWER
 | ||||
| 
 | ||||
| 	if (this->set_step_started(bspsGCodeFinalize)) { | ||||
| 	    if (! m_export_path.empty()) { | ||||
| 			wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); | ||||
| 	    	//FIXME localize the messages
 | ||||
| 	    	// Perform the final post-processing of the export path by applying the print statistics over the file name.
 | ||||
| 	    	std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path); | ||||
|  | @ -130,6 +132,7 @@ void BackgroundSlicingProcess::process_fff() | |||
| 	    	run_post_process_scripts(export_path, m_fff_print->config()); | ||||
| 	    	m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str()); | ||||
| 	    } else if (! m_upload_job.empty()) { | ||||
| 			wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); | ||||
| 			prepare_upload(); | ||||
| 	    } else { | ||||
| 			m_print->set_status(100, _utf8(L("Slicing complete"))); | ||||
|  | @ -155,6 +158,8 @@ void BackgroundSlicingProcess::process_sla() | |||
|     m_print->process(); | ||||
|     if (this->set_step_started(bspsGCodeFinalize)) { | ||||
|         if (! m_export_path.empty()) { | ||||
| 			wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); | ||||
| 
 | ||||
|             const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path); | ||||
| 
 | ||||
|             Zipper zipper(export_path); | ||||
|  | @ -176,6 +181,7 @@ void BackgroundSlicingProcess::process_sla() | |||
| 
 | ||||
|             m_print->set_status(100, (boost::format(_utf8(L("Masked SLA file exported to %1%"))) % export_path).str()); | ||||
|         } else if (! m_upload_job.empty()) { | ||||
| 			wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); | ||||
|             prepare_upload(); | ||||
|         } else { | ||||
| 			m_print->set_status(100, _utf8(L("Slicing complete"))); | ||||
|  |  | |||
|  | @ -69,6 +69,10 @@ public: | |||
| 	// The following wxCommandEvent will be sent to the UI thread / Plater window, when the G-code export is finished.
 | ||||
| 	// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
 | ||||
| 	void set_finished_event(int event_id) { m_event_finished_id = event_id; } | ||||
| 	// The following wxCommandEvent will be sent to the UI thread / Plater window, when the G-code is being exported to
 | ||||
| 	// specified path or uploaded.
 | ||||
| 	// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
 | ||||
| 	void set_export_began_event(int event_id) { m_event_export_began_id = event_id; } | ||||
| 
 | ||||
| 	// Activate either m_fff_print or m_sla_print.
 | ||||
| 	// Return true if changed.
 | ||||
|  | @ -204,6 +208,9 @@ private: | |||
| 	int 						m_event_slicing_completed_id 	= 0; | ||||
| 	// wxWidgets command ID to be sent to the plater to inform that the task finished.
 | ||||
| 	int 						m_event_finished_id  			= 0; | ||||
| 	// wxWidgets command ID to be sent to the plater to inform that the G-code is being exported.
 | ||||
| 	int                         m_event_export_began_id         = 0; | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| }; // namespace Slic3r
 | ||||
|  |  | |||
|  | @ -353,6 +353,7 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config) | |||
|     toggle_field("support_head_penetration", supports_en); | ||||
|     toggle_field("support_head_width", supports_en); | ||||
|     toggle_field("support_pillar_diameter", supports_en); | ||||
|     toggle_field("support_small_pillar_diameter_percent", supports_en); | ||||
|     toggle_field("support_max_bridges_on_pillar", supports_en); | ||||
|     toggle_field("support_pillar_connection_mode", supports_en); | ||||
|     toggle_field("support_buildplate_only", supports_en); | ||||
|  |  | |||
|  | @ -33,6 +33,7 @@ | |||
| #include "GUI_ObjectManipulation.hpp" | ||||
| #include "Mouse3DController.hpp" | ||||
| #include "I18N.hpp" | ||||
| #include "NotificationManager.hpp" | ||||
| 
 | ||||
| #if ENABLE_RETINA_GL | ||||
| #include "slic3r/Utils/RetinaHelper.hpp" | ||||
|  | @ -627,19 +628,45 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool | |||
| 
 | ||||
|         m_warnings.emplace_back(warning); | ||||
|         std::sort(m_warnings.begin(), m_warnings.end()); | ||||
| 
 | ||||
| 		std::string text; | ||||
| 		switch (warning) { | ||||
| 			case ObjectOutside: text = L("An object outside the print area was detected."); break; | ||||
| 			case ToolpathOutside: text = L("A toolpath outside the print area was detected."); break; | ||||
| 			case SlaSupportsOutside: text = L("SLA supports outside the print area were detected."); break; | ||||
| 			case SomethingNotShown: text = L("Some objects are not visible."); break; | ||||
| 			case ObjectClashed: wxGetApp().plater()->get_notification_manager()->push_plater_error_notification(L("An object outside the print area was detected.\n" | ||||
| 																												 "Resolve the current problem to continue slicing."),  | ||||
| 				                                                                                                *(wxGetApp().plater()->get_current_canvas3D())); | ||||
| 				break; | ||||
| 		} | ||||
| 		if (!text.empty()) | ||||
| 			wxGetApp().plater()->get_notification_manager()->push_plater_warning_notification(text, *(wxGetApp().plater()->get_current_canvas3D())); | ||||
|     } | ||||
|     else { | ||||
|         if (it == m_warnings.end()) // deactivating something that is not active is an easy task
 | ||||
|             return; | ||||
| 
 | ||||
|         m_warnings.erase(it); | ||||
|         if (m_warnings.empty()) { // nothing remains to be shown
 | ||||
| 
 | ||||
| 		std::string text; | ||||
| 		switch (warning) { | ||||
| 		case ObjectOutside: text = L("An object outside the print area was detected."); break; | ||||
| 		case ToolpathOutside: text = L("A toolpath outside the print area was detected."); break; | ||||
| 		case SlaSupportsOutside: text = L("SLA supports outside the print area were detected."); break; | ||||
| 		case SomethingNotShown: text = L("Some objects are not visibl.e"); break; | ||||
| 		case ObjectClashed: wxGetApp().plater()->get_notification_manager()->close_plater_error_notification(); break; | ||||
| 		} | ||||
| 		if (!text.empty()) | ||||
| 			wxGetApp().plater()->get_notification_manager()->close_plater_warning_notification(text); | ||||
| 
 | ||||
|         /*if (m_warnings.empty()) { // nothing remains to be shown
 | ||||
|             reset(); | ||||
|             m_msg_text = "";// save information for rescaling
 | ||||
|             return; | ||||
|         } | ||||
|         }*/ | ||||
|     } | ||||
| 
 | ||||
| 	/*
 | ||||
|     // Look at the end of our vector and generate proper texture.
 | ||||
|     std::string text; | ||||
|     bool red_colored = false; | ||||
|  | @ -661,6 +688,7 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool | |||
|     // save information for rescaling
 | ||||
|     m_msg_text = text; | ||||
|     m_is_colored_red = red_colored; | ||||
| 	*/ | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -2075,6 +2103,8 @@ void GLCanvas3D::render() | |||
| 
 | ||||
|     std::string tooltip; | ||||
| 
 | ||||
| 	 | ||||
| 
 | ||||
| 	// Negative coordinate means out of the window, likely because the window was deactivated.
 | ||||
| 	// In that case the tooltip should be hidden.
 | ||||
|     if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.)  | ||||
|  | @ -2104,6 +2134,8 @@ void GLCanvas3D::render() | |||
|         m_tooltip.render(m_mouse.position, *this); | ||||
| 
 | ||||
|     wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this); | ||||
| 	 | ||||
| 	wxGetApp().plater()->get_notification_manager()->render_notifications(*this); | ||||
| 
 | ||||
|     wxGetApp().imgui()->render(); | ||||
| 
 | ||||
|  | @ -3517,9 +3549,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) | |||
| #ifdef SLIC3R_DEBUG_MOUSE_EVENTS | ||||
|         printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str()); | ||||
| #endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ | ||||
| #if ENABLE_GCODE_VIEWER | ||||
|         m_dirty = true; | ||||
| #endif // ENABLE_GCODE_VIEWER
 | ||||
| 		 m_dirty = true; | ||||
|         // do not return if dragging or tooltip not empty to allow for tooltip update
 | ||||
|         if (!m_mouse.dragging && m_tooltip.is_empty()) | ||||
|             return; | ||||
|  | @ -3916,7 +3946,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) | |||
|         m_dirty = true; | ||||
| #else | ||||
|         // Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor hovers over.
 | ||||
|         if (m_picking_enabled) | ||||
|         //if (m_picking_enabled)
 | ||||
|             m_dirty = true; | ||||
| #endif // ENABLE_GCODE_VIEWER
 | ||||
|     } | ||||
|  |  | |||
|  | @ -54,6 +54,7 @@ | |||
| #include "Mouse3DController.hpp" | ||||
| #include "RemovableDriveManager.hpp" | ||||
| #include "InstanceCheck.hpp" | ||||
| #include "NotificationManager.hpp" | ||||
| 
 | ||||
| #ifdef __WXMSW__ | ||||
| #include <dbt.h> | ||||
|  | @ -384,7 +385,7 @@ bool GUI_App::on_init_inner() | |||
|     // supplied as argument to --datadir; in that case we should still run the wizard
 | ||||
|     preset_bundle->setup_directories(); | ||||
| 
 | ||||
| #ifdef __WXMSW__ | ||||
| #ifdef __WXMSW__  | ||||
|     associate_3mf_files(); | ||||
| #endif // __WXMSW__
 | ||||
| 
 | ||||
|  | @ -392,6 +393,11 @@ bool GUI_App::on_init_inner() | |||
|     Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent &evt) { | ||||
|         app_config->set("version_online", into_u8(evt.GetString())); | ||||
|         app_config->save(); | ||||
| 		if(this->plater_ != nullptr) { | ||||
| 			if (*Semver::parse(SLIC3R_VERSION) < * Semver::parse(into_u8(evt.GetString()))) { | ||||
| 				this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAviable, *(this->plater_->get_current_canvas3D())); | ||||
| 			} | ||||
| 		} | ||||
|     }); | ||||
| 
 | ||||
|     // initialize label colors and fonts
 | ||||
|  | @ -1453,7 +1459,7 @@ void GUI_App::check_updates(const bool verbose) | |||
| 	 | ||||
| 	PresetUpdater::UpdateResult updater_result; | ||||
| 	try { | ||||
| 		updater_result = preset_updater->config_update(app_config->orig_version()); | ||||
| 		updater_result = preset_updater->config_update(app_config->orig_version(), verbose); | ||||
| 		if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) { | ||||
| 			mainframe->Close(); | ||||
| 		} | ||||
|  |  | |||
|  | @ -198,12 +198,15 @@ public: | |||
|     Plater*             plater(); | ||||
|     Model&      		model(); | ||||
| 
 | ||||
| 
 | ||||
|     AppConfig*      app_config{ nullptr }; | ||||
|     PresetBundle*   preset_bundle{ nullptr }; | ||||
|     PresetUpdater*  preset_updater{ nullptr }; | ||||
|     MainFrame*      mainframe{ nullptr }; | ||||
|     Plater*         plater_{ nullptr }; | ||||
| 
 | ||||
| 	PresetUpdater* get_preset_updater() { return preset_updater; } | ||||
| 
 | ||||
|     wxNotebook*     tab_panel() const ; | ||||
|     int             extruders_cnt() const; | ||||
|     int             extruders_edited_cnt() const; | ||||
|  |  | |||
|  | @ -37,11 +37,17 @@ namespace GUI { | |||
| 
 | ||||
| 
 | ||||
| static const std::map<const char, std::string> font_icons = { | ||||
|     {ImGui::PrintIconMarker     , "cog"        }, | ||||
|     {ImGui::PrinterIconMarker   , "printer"    }, | ||||
|     {ImGui::PrinterSlaIconMarker, "sla_printer"}, | ||||
|     {ImGui::FilamentIconMarker  , "spool"      }, | ||||
|     {ImGui::MaterialIconMarker  , "resin"      } | ||||
|     {ImGui::PrintIconMarker     , "cog"               }, | ||||
|     {ImGui::PrinterIconMarker   , "printer"           }, | ||||
|     {ImGui::PrinterSlaIconMarker, "sla_printer"       }, | ||||
|     {ImGui::FilamentIconMarker  , "spool"             }, | ||||
|     {ImGui::MaterialIconMarker  , "resin"             }, | ||||
| 	{ImGui::CloseIconMarker     , "cross"             }, | ||||
| 	{ImGui::CloseIconHoverMarker, "cross_focus_large" }, | ||||
| 	{ImGui::TimerDotMarker      , "timer_dot"         }, | ||||
|     {ImGui::TimerDotEmptyMarker , "timer_dot_empty"   }, | ||||
| 	{ImGui::WarningMarker       , "flag_green"        }, | ||||
|     {ImGui::ErrorMarker         , "flag_red"          } | ||||
| }; | ||||
| 
 | ||||
| const ImVec4 ImGuiWrapper::COL_WINDOW_BACKGROND = { 0.133f, 0.133f, 0.133f, 0.8f }; | ||||
|  | @ -271,6 +277,11 @@ void ImGuiWrapper::set_next_window_bg_alpha(float alpha) | |||
|     ImGui::SetNextWindowBgAlpha(alpha); | ||||
| } | ||||
| 
 | ||||
| void ImGuiWrapper::set_next_window_size(float x, float y, ImGuiCond cond) | ||||
| { | ||||
| 	ImGui::SetNextWindowSize(ImVec2(x, y), cond); | ||||
| } | ||||
| 
 | ||||
| bool ImGuiWrapper::begin(const std::string &name, int flags) | ||||
| { | ||||
|     return ImGui::Begin(name.c_str(), nullptr, (ImGuiWindowFlags)flags); | ||||
|  | @ -302,12 +313,23 @@ bool ImGuiWrapper::button(const wxString &label) | |||
|     return ImGui::Button(label_utf8.c_str()); | ||||
| } | ||||
| 
 | ||||
| bool ImGuiWrapper::button(const wxString& label, float width, float height) | ||||
| { | ||||
| 	auto label_utf8 = into_u8(label); | ||||
| 	return ImGui::Button(label_utf8.c_str(), ImVec2(width, height)); | ||||
| } | ||||
| 
 | ||||
| bool ImGuiWrapper::radio_button(const wxString &label, bool active) | ||||
| { | ||||
|     auto label_utf8 = into_u8(label); | ||||
|     return ImGui::RadioButton(label_utf8.c_str(), active); | ||||
| } | ||||
| 
 | ||||
| bool ImGuiWrapper::image_button() | ||||
| { | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format) | ||||
| { | ||||
|     return ImGui::InputDouble(label.c_str(), const_cast<double*>(&value), 0.0f, 0.0f, format.c_str()); | ||||
|  |  | |||
|  | @ -57,6 +57,7 @@ public: | |||
| 
 | ||||
|     void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f); | ||||
|     void set_next_window_bg_alpha(float alpha); | ||||
| 	void set_next_window_size(float x, float y, ImGuiCond cond); | ||||
| 
 | ||||
|     bool begin(const std::string &name, int flags = 0); | ||||
|     bool begin(const wxString &name, int flags = 0); | ||||
|  | @ -65,7 +66,9 @@ public: | |||
|     void end(); | ||||
| 
 | ||||
|     bool button(const wxString &label); | ||||
| 	bool button(const wxString& label, float width, float height); | ||||
|     bool radio_button(const wxString &label, bool active); | ||||
| 	bool image_button(); | ||||
|     bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f"); | ||||
|     bool input_double(const wxString &label, const double &value, const std::string &format = "%.3f"); | ||||
|     bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f"); | ||||
|  |  | |||
|  | @ -134,7 +134,7 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& | |||
|     Vec3d direction; | ||||
|     line_from_mouse_pos(mouse_pos, trafo, camera, point, direction); | ||||
| 
 | ||||
|     std::vector<sla::EigenMesh3D::hit_result> hits = m_emesh.query_ray_hits(point, direction); | ||||
|     std::vector<sla::IndexedMesh::hit_result> hits = m_emesh.query_ray_hits(point, direction); | ||||
| 
 | ||||
|     if (hits.empty()) | ||||
|         return false; // no intersection found
 | ||||
|  | @ -184,7 +184,7 @@ std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo | |||
| 
 | ||||
|         bool is_obscured = false; | ||||
|         // Cast a ray in the direction of the camera and look for intersection with the mesh:
 | ||||
|         std::vector<sla::EigenMesh3D::hit_result> hits; | ||||
|         std::vector<sla::IndexedMesh::hit_result> hits; | ||||
|         // Offset the start of the ray by EPSILON to account for numerical inaccuracies.
 | ||||
|         hits = m_emesh.query_ray_hits((inverse_trafo * pt + direction_to_camera_mesh * EPSILON).cast<double>(), | ||||
|                                       direction_to_camera.cast<double>()); | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ | |||
| 
 | ||||
| #include "libslic3r/Point.hpp" | ||||
| #include "libslic3r/Geometry.hpp" | ||||
| #include "libslic3r/SLA/EigenMesh3D.hpp" | ||||
| #include "libslic3r/SLA/IndexedMesh.hpp" | ||||
| #include "admesh/stl.h" | ||||
| 
 | ||||
| #include "slic3r/GUI/3DScene.hpp" | ||||
|  | @ -147,7 +147,7 @@ public: | |||
|     Vec3f get_triangle_normal(size_t facet_idx) const; | ||||
| 
 | ||||
| private: | ||||
|     sla::EigenMesh3D m_emesh; | ||||
|     sla::IndexedMesh m_emesh; | ||||
|     std::vector<stl_normal> m_normals; | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ | |||
| #include "AppConfig.hpp" | ||||
| #include "GLCanvas3D.hpp" | ||||
| #include "Plater.hpp" | ||||
| #include "NotificationManager.hpp" | ||||
| 
 | ||||
| #include <wx/glcanvas.h> | ||||
| 
 | ||||
|  | @ -403,6 +404,8 @@ void Mouse3DController::disconnected() | |||
|         m_params_by_device[m_device_str] = m_params_ui; | ||||
| 	    m_device_str.clear(); | ||||
| 	    m_connected = false; | ||||
| 		wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::Mouse3dDisconnected, *(wxGetApp().plater()->get_current_canvas3D())); | ||||
| 
 | ||||
|         wxGetApp().plater()->CallAfter([]() { | ||||
|         	Plater *plater = wxGetApp().plater(); | ||||
|         	if (plater != nullptr) { | ||||
|  |  | |||
							
								
								
									
										918
									
								
								src/slic3r/GUI/NotificationManager.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										918
									
								
								src/slic3r/GUI/NotificationManager.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,918 @@ | |||
| #include "NotificationManager.hpp" | ||||
| 
 | ||||
| #include "GUI_App.hpp" | ||||
| #include "Plater.hpp" | ||||
| #include "GLCanvas3D.hpp" | ||||
| #include "ImGuiWrapper.hpp" | ||||
| 
 | ||||
| #include "wxExtensions.hpp" | ||||
| 
 | ||||
| #include <boost/algorithm/string.hpp> | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <wx/glcanvas.h> | ||||
| #include <iostream> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| #define NOTIFICATION_MAX_MOVE 3.0f | ||||
| 
 | ||||
| #define GAP_WIDTH 10.0f | ||||
| #define SPACE_RIGHT_PANEL 10.0f | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
| 
 | ||||
| wxDEFINE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClickedEvent); | ||||
| wxDEFINE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent); | ||||
| wxDEFINE_EVENT(EVT_PRESET_UPDATE_AVIABLE_CLICKED, PresetUpdateAviableClickedEvent); | ||||
| 
 | ||||
| namespace Notifications_Internal{ | ||||
| 	void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, float current_fade_opacity) | ||||
| 	{ | ||||
| 		if (fading_out) | ||||
| 			ImGui::PushStyleColor(idx, ImVec4(col.x, col.y, col.z, col.w * current_fade_opacity)); | ||||
| 		else | ||||
| 			ImGui::PushStyleColor(idx, col); | ||||
| 	} | ||||
| } | ||||
| //ScalableBitmap bmp_icon;
 | ||||
| //------PopNotification--------
 | ||||
| NotificationManager::PopNotification::PopNotification(const NotificationData &n, const int id, wxEvtHandler* evt_handler) : | ||||
| 	  m_data                (n) | ||||
| 	, m_id                  (id)     | ||||
| 	, m_remaining_time      (n.duration) | ||||
| 	, m_last_remaining_time (n.duration) | ||||
| 	, m_counting_down       (n.duration != 0) | ||||
| 	, m_text1               (n.text1) | ||||
|     , m_hypertext           (n.hypertext) | ||||
|     , m_text2               (n.text2) | ||||
| 	, m_evt_handler         (evt_handler) | ||||
| { | ||||
| 	init(); | ||||
| } | ||||
| NotificationManager::PopNotification::~PopNotification() | ||||
| { | ||||
| } | ||||
| NotificationManager::PopNotification::RenderResult NotificationManager::PopNotification::render(GLCanvas3D& canvas, const float& initial_y) | ||||
| { | ||||
| 	if (m_finished) | ||||
| 		return RenderResult::Finished; | ||||
| 	if (m_close_pending) { | ||||
| 		// request of extra frame will be done in caller function by ret val ClosePending
 | ||||
| 		m_finished = true; | ||||
| 		return RenderResult::ClosePending; | ||||
| 	} | ||||
| 	if (m_hidden) { | ||||
| 		m_top_y = initial_y - GAP_WIDTH; | ||||
| 		return RenderResult::Static; | ||||
| 	} | ||||
| 	RenderResult    ret_val = m_counting_down ? RenderResult::Countdown : RenderResult::Static; | ||||
| 	Size            cnv_size = canvas.get_canvas_size(); | ||||
| 	ImGuiWrapper&   imgui = *wxGetApp().imgui(); | ||||
| 	bool            shown = true; | ||||
| 	std::string     name; | ||||
| 	ImVec2          mouse_pos = ImGui::GetMousePos(); | ||||
| 
 | ||||
| 	if (m_line_height != ImGui::CalcTextSize("A").y) | ||||
| 		init(); | ||||
| 	 | ||||
| 	set_next_window_size(imgui); | ||||
| 
 | ||||
| 	//top y of window
 | ||||
| 	m_top_y = initial_y + m_window_height; | ||||
| 	//top right position
 | ||||
| 	ImVec2 win_pos(1.0f * (float)cnv_size.get_width() - SPACE_RIGHT_PANEL, 1.0f * (float)cnv_size.get_height() - m_top_y); | ||||
| 	imgui.set_next_window_pos(win_pos.x, win_pos.y, ImGuiCond_Always, 1.0f, 0.0f); | ||||
| 	imgui.set_next_window_size(m_window_width, m_window_height, ImGuiCond_Always); | ||||
| 
 | ||||
| 	//find if hovered
 | ||||
| 	if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y&& mouse_pos.y < win_pos.y + m_window_height) | ||||
| 	{ | ||||
| 		ImGui::SetNextWindowFocus(); | ||||
| 		ret_val = RenderResult::Hovered; | ||||
| 		//reset fading
 | ||||
| 		m_fading_out = false; | ||||
| 		m_current_fade_opacity = 1.f; | ||||
| 		m_remaining_time = m_data.duration; | ||||
| 		m_countdown_frame = 0; | ||||
| 	} | ||||
| 
 | ||||
| 	if (m_counting_down && m_remaining_time < 0) | ||||
| 		m_close_pending = true; | ||||
| 
 | ||||
| 	if (m_close_pending) { | ||||
| 		// request of extra frame will be done in caller function by ret val ClosePending
 | ||||
| 		m_finished = true; | ||||
| 		return RenderResult::ClosePending; | ||||
| 	} | ||||
| 
 | ||||
| 	// color change based on fading out
 | ||||
| 	bool fading_pop = false; | ||||
| 	if (m_fading_out) { | ||||
| 		if (!m_paused) | ||||
| 			m_current_fade_opacity -= 1.f / ((m_fading_time + 1.f) * 60.f); | ||||
| 		Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity); | ||||
| 		Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity); | ||||
| 		fading_pop = true; | ||||
| 	} | ||||
| 	// background color
 | ||||
| 	if (m_is_gray) { | ||||
| 		ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f); | ||||
| 		Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); | ||||
| 	} else if (m_data.level == NotificationLevel::ErrorNotification) { | ||||
| 		ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); | ||||
| 		backcolor.x += 0.3f; | ||||
| 		Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); | ||||
| 	} else if (m_data.level == NotificationLevel::WarningNotification) { | ||||
| 		ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); | ||||
| 		backcolor.x += 0.3f; | ||||
| 		backcolor.y += 0.15f; | ||||
| 		Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); | ||||
| 	} | ||||
| 
 | ||||
| 	//name of window - probably indentifies window and is shown so last_end add whitespaces according to id
 | ||||
| 	for (size_t i = 0; i < m_id; i++) | ||||
| 		name += " "; | ||||
| 	if (imgui.begin(name, &shown, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar )) { | ||||
| 		if (shown) { | ||||
| 			 | ||||
| 			ImVec2 win_size = ImGui::GetWindowSize(); | ||||
| 			 | ||||
| 			 | ||||
| 			//FIXME: dont forget to us this for texts
 | ||||
| 			//GUI::format(_utf8(L()));
 | ||||
| 			 | ||||
| 			/*
 | ||||
| 			//countdown numbers
 | ||||
| 			ImGui::SetCursorPosX(15); | ||||
| 			ImGui::SetCursorPosY(15); | ||||
| 			imgui.text(std::to_string(m_remaining_time).c_str()); | ||||
| 			*/ | ||||
| 			if(m_counting_down) | ||||
| 				render_countdown(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y); | ||||
| 			render_left_sign(imgui); | ||||
| 			render_text(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y); | ||||
| 			render_close_button(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y); | ||||
| 			if (m_multiline && m_lines_count > 3) | ||||
| 				render_minimize_button(imgui, win_pos.x, win_pos.y); | ||||
| 		} else { | ||||
| 			// the user clicked on the [X] button ( ImGuiWindowFlags_NoTitleBar means theres no [X] button)
 | ||||
| 			m_close_pending = true; | ||||
| 			canvas.set_as_dirty(); | ||||
| 		} | ||||
| 	} | ||||
| 	imgui.end(); | ||||
| 	 | ||||
| 	if (fading_pop) { | ||||
| 		ImGui::PopStyleColor(); | ||||
| 		ImGui::PopStyleColor(); | ||||
| 	} | ||||
| 	if (m_is_gray) | ||||
| 		ImGui::PopStyleColor(); | ||||
| 	else if (m_data.level == NotificationLevel::ErrorNotification) | ||||
| 		ImGui::PopStyleColor(); | ||||
| 	else if (m_data.level == NotificationLevel::WarningNotification) | ||||
| 		ImGui::PopStyleColor(); | ||||
| 	return ret_val; | ||||
| } | ||||
| void NotificationManager::PopNotification::init() | ||||
| { | ||||
| 	std::string text          = m_text1 + " " + m_hypertext; | ||||
| 	int         last_end      = 0; | ||||
| 	            m_lines_count = 0; | ||||
| 
 | ||||
| 	//determine line width 
 | ||||
| 	m_line_height = ImGui::CalcTextSize("A").y; | ||||
| 
 | ||||
| 	m_left_indentation = m_line_height; | ||||
| 	if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) { | ||||
| 		std::string text; | ||||
| 		text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); | ||||
| 		float picture_width = ImGui::CalcTextSize(text.c_str()).x; | ||||
| 		m_left_indentation = picture_width + m_line_height / 2; | ||||
| 	} | ||||
| 	m_window_width_offset = m_left_indentation + m_line_height * 2; | ||||
| 	m_window_width = m_line_height * 25; | ||||
| 	 | ||||
| 	// count lines
 | ||||
| 	m_endlines.clear(); | ||||
| 	while (last_end < text.length() - 1) | ||||
| 	{ | ||||
| 		int next_hard_end = text.find_first_of('\n', last_end); | ||||
| 		if (next_hard_end > 0 && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset) { | ||||
| 			//next line is ended by '/n'
 | ||||
| 			m_endlines.push_back(next_hard_end); | ||||
| 			last_end = next_hard_end + 1; | ||||
| 		} | ||||
| 		else { | ||||
| 			// find next suitable endline
 | ||||
| 			if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - 3.5f * m_line_height) {// m_window_width_offset) {
 | ||||
| 				// more than one line till end
 | ||||
| 				int next_space = text.find_first_of(' ', last_end); | ||||
| 				if (next_space > 0) { | ||||
| 					int next_space_candidate = text.find_first_of(' ', next_space + 1); | ||||
| 					while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset) { | ||||
| 						next_space = next_space_candidate; | ||||
| 						next_space_candidate = text.find_first_of(' ', next_space + 1); | ||||
| 					} | ||||
| 					m_endlines.push_back(next_space); | ||||
| 					last_end = next_space + 1; | ||||
| 				} | ||||
| 			} | ||||
| 			else { | ||||
| 				m_endlines.push_back(text.length()); | ||||
| 				last_end = text.length(); | ||||
| 			} | ||||
| 
 | ||||
| 		} | ||||
| 		m_lines_count++; | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui) | ||||
| {  | ||||
| 	if (m_multiline) { | ||||
| 		m_window_height = m_lines_count * m_line_height; | ||||
| 	}else | ||||
| 	{ | ||||
| 		m_window_height = 2 * m_line_height; | ||||
| 	} | ||||
| 	m_window_height += 1 * m_line_height; // top and bottom
 | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::PopNotification::render_text(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); | ||||
| 	float       x_offset = m_left_indentation; | ||||
| 	std::string fulltext = m_text1 + m_hypertext; //+ m_text2;
 | ||||
| 	ImVec2      text_size = ImGui::CalcTextSize(fulltext.c_str()); | ||||
| 	// text posistions are calculated by lines count
 | ||||
| 	// large texts has "more" button or are displayed whole
 | ||||
| 	// smaller texts are divided as one liners and two liners
 | ||||
| 	if (m_lines_count > 2) { | ||||
| 		if (m_multiline) { | ||||
| 			 | ||||
| 			int last_end = 0; | ||||
| 			float starting_y = m_line_height/2;//10;
 | ||||
| 			float shift_y = m_line_height;// -m_line_height / 20;
 | ||||
| 			for (size_t i = 0; i < m_lines_count; i++) { | ||||
| 			    std::string line = m_text1.substr(last_end , m_endlines[i] - last_end); | ||||
| 				last_end = m_endlines[i] + 1; | ||||
| 				ImGui::SetCursorPosX(x_offset); | ||||
| 				ImGui::SetCursorPosY(starting_y + i * shift_y); | ||||
| 				imgui.text(line.c_str()); | ||||
| 			} | ||||
| 			//hyperlink text
 | ||||
| 			if (!m_hypertext.empty()) | ||||
| 			{ | ||||
| 				render_hypertext(imgui, x_offset + ImGui::CalcTextSize(m_text1.substr(m_endlines[m_lines_count - 2] + 1, m_endlines[m_lines_count - 1] - m_endlines[m_lines_count - 2] - 1).c_str()).x, starting_y + (m_lines_count - 1) * shift_y, m_hypertext); | ||||
| 			} | ||||
| 			 | ||||
| 			 | ||||
| 		} else { | ||||
| 			// line1
 | ||||
| 			ImGui::SetCursorPosX(x_offset); | ||||
| 			ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2); | ||||
| 			imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); | ||||
| 			// line2
 | ||||
| 			std::string line = m_text1.substr(m_endlines[0] + 1, m_endlines[1] - m_endlines[0] - 1); | ||||
| 			if (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x) | ||||
| 			{ | ||||
| 				line = line.substr(0, line.length() - 6); | ||||
| 				line += ".."; | ||||
| 			}else | ||||
| 				line += "  "; | ||||
| 			ImGui::SetCursorPosX(x_offset); | ||||
| 			ImGui::SetCursorPosY(win_size.y / 2 + win_size.y / 6 - m_line_height / 2); | ||||
| 			imgui.text(line.c_str()); | ||||
| 			// "More" hypertext
 | ||||
| 			render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x, win_size.y / 2 + win_size.y / 6 - m_line_height / 2, _u8L("More"), true); | ||||
| 		} | ||||
| 	} else { | ||||
| 		//text 1
 | ||||
| 		float cursor_y = win_size.y / 2 - text_size.y / 2; | ||||
| 		float cursor_x = x_offset; | ||||
| 		if(m_lines_count > 1) { | ||||
| 			// line1
 | ||||
| 			ImGui::SetCursorPosX(x_offset); | ||||
| 			ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2); | ||||
| 			imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); | ||||
| 			// line2
 | ||||
| 			std::string line = m_text1.substr(m_endlines[0] + 1); | ||||
| 			cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2; | ||||
| 			ImGui::SetCursorPosX(x_offset); | ||||
| 			ImGui::SetCursorPosY(cursor_y); | ||||
| 			imgui.text(line.c_str()); | ||||
| 			cursor_x = x_offset + ImGui::CalcTextSize(line.c_str()).x; | ||||
| 		} else { | ||||
| 			ImGui::SetCursorPosX(x_offset); | ||||
| 			ImGui::SetCursorPosY(cursor_y); | ||||
| 			imgui.text(m_text1.c_str()); | ||||
| 			cursor_x = x_offset + ImGui::CalcTextSize(m_text1.c_str()).x; | ||||
| 		} | ||||
| 		//hyperlink text
 | ||||
| 		if (!m_hypertext.empty()) | ||||
| 		{ | ||||
| 			render_hypertext(imgui, cursor_x + 4, cursor_y, m_hypertext); | ||||
| 		} | ||||
| 
 | ||||
| 		//notification text 2
 | ||||
| 		//text 2 is suposed to be after the hyperlink - currently it is not used
 | ||||
| 		/*
 | ||||
| 		if (!m_text2.empty()) | ||||
| 		{ | ||||
| 			ImVec2 part_size = ImGui::CalcTextSize(m_hypertext.c_str()); | ||||
| 			ImGui::SetCursorPosX(win_size.x / 2 + text_size.x / 2 - part_size.x + 8 - x_offset); | ||||
| 			ImGui::SetCursorPosY(cursor_y); | ||||
| 			imgui.text(m_text2.c_str()); | ||||
| 		} | ||||
| 		*/ | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, const float text_x, const float text_y, const std::string text, bool more) | ||||
| { | ||||
| 	//invisible button
 | ||||
| 	ImVec2 part_size = ImGui::CalcTextSize(text.c_str()); | ||||
| 	ImGui::SetCursorPosX(text_x -4); | ||||
| 	ImGui::SetCursorPosY(text_y -5); | ||||
| 	ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); | ||||
| 	ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); | ||||
| 	ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); | ||||
| 	if (imgui.button("   ", part_size.x + 6, part_size.y + 10)) | ||||
| 	{ | ||||
| 		if (more) | ||||
| 		{ | ||||
| 			m_multiline = true; | ||||
| 			set_next_window_size(imgui); | ||||
| 		} | ||||
| 		else { | ||||
| 			on_text_click(); | ||||
| 			m_close_pending = true; | ||||
| 		} | ||||
| 	} | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	ImGui::PopStyleColor(); | ||||
| 
 | ||||
| 	//hover color
 | ||||
| 	ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button); | ||||
| 	if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)) | ||||
| 		orange_color.y += 0.2f; | ||||
| 
 | ||||
| 	//text
 | ||||
| 	Notifications_Internal::push_style_color(ImGuiCol_Text, orange_color, m_fading_out, m_current_fade_opacity); | ||||
| 	ImGui::SetCursorPosX(text_x); | ||||
| 	ImGui::SetCursorPosY(text_y); | ||||
| 	imgui.text(text.c_str()); | ||||
| 	ImGui::PopStyleColor(); | ||||
| 
 | ||||
| 	//underline
 | ||||
| 	ImVec2 lineEnd = ImGui::GetItemRectMax(); | ||||
| 	lineEnd.y -= 2; | ||||
| 	ImVec2 lineStart = lineEnd; | ||||
| 	lineStart.x = ImGui::GetItemRectMin().x; | ||||
| 	ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.w * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f)))); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::PopNotification::render_close_button(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); | ||||
| 	ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button); | ||||
| 	orange_color.w = 0.8f; | ||||
| 	ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); | ||||
| 	ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); | ||||
| 	Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); | ||||
| 	Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); | ||||
| 	ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); | ||||
| 
 | ||||
| 
 | ||||
| 	//button - if part if treggered
 | ||||
| 	std::string button_text; | ||||
| 	button_text = ImGui::CloseIconMarker; | ||||
| 	 | ||||
| 	if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y), | ||||
| 		                           ImVec2(win_pos.x, win_pos.y + win_size.y - (m_multiline? 2 * m_line_height : 0)), | ||||
| 		                           true)) | ||||
| 	{ | ||||
| 		button_text = ImGui::CloseIconHoverMarker; | ||||
| 	} | ||||
| 	ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); | ||||
| 	ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); | ||||
| 	ImGui::SetCursorPosX(win_size.x - m_line_height * 2.25f); | ||||
| 	ImGui::SetCursorPosY(win_size.y / 2 - button_size.y/2); | ||||
| 	if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) | ||||
| 	{ | ||||
| 		m_close_pending = true; | ||||
| 	} | ||||
| 
 | ||||
| 	//invisible large button
 | ||||
| 	ImGui::SetCursorPosX(win_size.x - win_size.x / 10.f); | ||||
| 	ImGui::SetCursorPosY(0); | ||||
| 	if (imgui.button(" ", win_size.x / 10.f, win_size.y - (m_multiline ? 2 * m_line_height : 0))) | ||||
| 	{ | ||||
| 		m_close_pending = true; | ||||
| 	} | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	ImGui::PopStyleColor(); | ||||
| } | ||||
| void NotificationManager::PopNotification::render_countdown(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); | ||||
| 	 | ||||
| 	//countdown dots
 | ||||
| 	std::string dot_text; | ||||
| 	dot_text = m_remaining_time <= (float)m_data.duration / 4 * 3 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker; | ||||
| 	ImGui::SetCursorPosX(win_size.x - m_line_height); | ||||
| 	//ImGui::SetCursorPosY(win_size.y / 2 - 24);
 | ||||
| 	ImGui::SetCursorPosY(0); | ||||
| 	imgui.text(dot_text.c_str()); | ||||
| 
 | ||||
| 	dot_text = m_remaining_time < m_data.duration / 2 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker; | ||||
| 	ImGui::SetCursorPosX(win_size.x - m_line_height); | ||||
| 	//ImGui::SetCursorPosY(win_size.y / 2 - 9);
 | ||||
| 	ImGui::SetCursorPosY(win_size.y / 2 - m_line_height / 2); | ||||
| 	imgui.text(dot_text.c_str()); | ||||
| 
 | ||||
| 	dot_text = m_remaining_time <= m_data.duration / 4 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker; | ||||
| 	ImGui::SetCursorPosX(win_size.x - m_line_height); | ||||
| 	//ImGui::SetCursorPosY(win_size.y / 2 + 6);
 | ||||
| 	ImGui::SetCursorPosY(win_size.y - m_line_height); | ||||
| 	imgui.text(dot_text.c_str()); | ||||
| 	*/ | ||||
| 	if (!m_fading_out && m_remaining_time <= m_data.duration / 4) { | ||||
| 		m_fading_out = true; | ||||
| 		m_fading_time = m_remaining_time; | ||||
| 	} | ||||
| 	 | ||||
| 	if (m_last_remaining_time != m_remaining_time) { | ||||
| 		m_last_remaining_time = m_remaining_time; | ||||
| 		m_countdown_frame = 0; | ||||
| 	} | ||||
| 	/*
 | ||||
| 	//countdown line
 | ||||
| 	ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button); | ||||
| 	float  invisible_length = ((float)(m_data.duration - m_remaining_time) / (float)m_data.duration * win_size_x); | ||||
| 	invisible_length -= win_size_x / ((float)m_data.duration * 60.f) * (60 - m_countdown_frame); | ||||
| 	ImVec2 lineEnd = ImVec2(win_pos_x - invisible_length, win_pos_y + win_size_y - 5); | ||||
| 	ImVec2 lineStart = ImVec2(win_pos_x - win_size_x, win_pos_y + win_size_y - 5); | ||||
| 	ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.picture_width * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f))), 2.f); | ||||
| 	if (!m_paused) | ||||
| 		m_countdown_frame++; | ||||
| 		*/ | ||||
| } | ||||
| void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui) | ||||
| { | ||||
| 	if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) { | ||||
| 		std::string text; | ||||
| 		text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); | ||||
| 		ImGui::SetCursorPosX(m_line_height / 3); | ||||
| 		ImGui::SetCursorPosY(m_window_height / 2 - m_line_height / 2); | ||||
| 		imgui.text(text.c_str()); | ||||
| 	}  | ||||
| } | ||||
| void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) | ||||
| { | ||||
| 	ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button); | ||||
| 	orange_color.w = 0.8f; | ||||
| 	ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); | ||||
| 	ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); | ||||
| 	Notifications_Internal::push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity); | ||||
| 	Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); | ||||
| 	Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); | ||||
| 
 | ||||
| 	 | ||||
| 	//button - if part if treggered
 | ||||
| 	std::string button_text; | ||||
| 	button_text = ImGui::CloseIconMarker; | ||||
| 	if (ImGui::IsMouseHoveringRect(ImVec2(win_pos_x - m_window_width / 10.f, win_pos_y + m_window_height - 2 * m_line_height + 1), | ||||
| 		ImVec2(win_pos_x, win_pos_y + m_window_height), | ||||
| 		true))  | ||||
| 	{ | ||||
| 		button_text = ImGui::CloseIconHoverMarker; | ||||
| 	} | ||||
| 	ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); | ||||
| 	ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); | ||||
| 	ImGui::SetCursorPosX(m_window_width - m_line_height * 2.25f); | ||||
| 	ImGui::SetCursorPosY(m_window_height - button_size.y - 5); | ||||
| 	if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) | ||||
| 	{ | ||||
| 		m_multiline = false; | ||||
| 	} | ||||
| 	 | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	ImGui::PopStyleColor(); | ||||
| } | ||||
| void NotificationManager::PopNotification::on_text_click() | ||||
| { | ||||
| 	switch (m_data.type) { | ||||
| 	case NotificationType::ExportToRemovableFinished : | ||||
| 		assert(m_evt_handler != nullptr); | ||||
| 		if (m_evt_handler != nullptr) | ||||
| 			wxPostEvent(m_evt_handler, EjectDriveNotificationClickedEvent(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED)); | ||||
| 		break; | ||||
| 	case NotificationType::SlicingComplete : | ||||
| 		//wxGetApp().plater()->export_gcode(false);
 | ||||
| 		assert(m_evt_handler != nullptr); | ||||
| 		if (m_evt_handler != nullptr) | ||||
| 			wxPostEvent(m_evt_handler, ExportGcodeNotificationClickedEvent(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED)); | ||||
| 		break; | ||||
| 	case NotificationType::PresetUpdateAviable : | ||||
| 		//wxGetApp().plater()->export_gcode(false);
 | ||||
| 		assert(m_evt_handler != nullptr); | ||||
| 		if (m_evt_handler != nullptr) | ||||
| 			wxPostEvent(m_evt_handler, PresetUpdateAviableClickedEvent(EVT_PRESET_UPDATE_AVIABLE_CLICKED)); | ||||
| 		break; | ||||
| 	case NotificationType::NewAppAviable: | ||||
| 		wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases"); | ||||
| 		break; | ||||
| 	default: | ||||
| 		break; | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::PopNotification::update(const NotificationData& n) | ||||
| { | ||||
| 	m_text1          = n.text1; | ||||
| 	m_hypertext      = n.hypertext; | ||||
|     m_text2          = n.text2; | ||||
| 	init(); | ||||
| } | ||||
| bool NotificationManager::PopNotification::compare_text(const std::string& text) | ||||
| { | ||||
| 	std::string t1(m_text1); | ||||
| 	std::string t2(text); | ||||
| 	t1.erase(std::remove_if(t1.begin(), t1.end(), ::isspace), t1.end()); | ||||
| 	t2.erase(std::remove_if(t2.begin(), t2.end(), ::isspace), t2.end()); | ||||
| 	if (t1.compare(t2) == 0) | ||||
| 		return true; | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| NotificationManager::SlicingCompleteLargeNotification::SlicingCompleteLargeNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler, bool large) : | ||||
| 	  NotificationManager::PopNotification(n, id, evt_handler) | ||||
| { | ||||
| 	set_large(large); | ||||
| } | ||||
| void NotificationManager::SlicingCompleteLargeNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) | ||||
| { | ||||
| 	if (!m_is_large) | ||||
| 		PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); | ||||
| 	else { | ||||
| 		ImVec2 win_size(win_size_x, win_size_y); | ||||
| 		ImVec2 win_pos(win_pos_x, win_pos_y); | ||||
| 
 | ||||
| 		ImVec2 text1_size = ImGui::CalcTextSize(m_text1.c_str()); | ||||
| 		float x_offset = m_left_indentation; | ||||
| 		std::string fulltext = m_text1 + m_hypertext + m_text2; | ||||
| 		ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str()); | ||||
| 		float cursor_y = win_size.y / 2 - text_size.y / 2; | ||||
| 		if (m_has_print_info) { | ||||
| 			x_offset = 20; | ||||
| 			cursor_y = win_size.y / 2 + win_size.y / 6 - text_size.y / 2; | ||||
| 			ImGui::SetCursorPosX(x_offset); | ||||
| 			ImGui::SetCursorPosY(cursor_y); | ||||
| 			imgui.text(m_print_info.c_str()); | ||||
| 			cursor_y = win_size.y / 2 - win_size.y / 6 - text_size.y / 2; | ||||
| 		} | ||||
| 		ImGui::SetCursorPosX(x_offset); | ||||
| 		ImGui::SetCursorPosY(cursor_y); | ||||
| 		imgui.text(m_text1.c_str()); | ||||
| 
 | ||||
| 		render_hypertext(imgui, x_offset + text1_size.x + 4, cursor_y, m_hypertext); | ||||
| 		 | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::SlicingCompleteLargeNotification::set_print_info(std::string info) | ||||
| { | ||||
| 	m_print_info = info; | ||||
| 	m_has_print_info = true; | ||||
| 	if(m_is_large) | ||||
| 		m_lines_count = 2; | ||||
| } | ||||
| void NotificationManager::SlicingCompleteLargeNotification::set_large(bool l) | ||||
| { | ||||
| 	m_is_large = l; | ||||
| 	m_counting_down = !l; | ||||
| 	m_hypertext = l ? _u8L("Export G-Code.") : std::string(); | ||||
| 	m_hidden = !l; | ||||
| } | ||||
| //------NotificationManager--------
 | ||||
| NotificationManager::NotificationManager(wxEvtHandler* evt_handler) : | ||||
| 	m_evt_handler(evt_handler) | ||||
| { | ||||
| } | ||||
| NotificationManager::~NotificationManager() | ||||
| { | ||||
| 	for (PopNotification* notification : m_pop_notifications) | ||||
| 	{ | ||||
| 		delete notification; | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::push_notification(const NotificationType type, GLCanvas3D& canvas, int timestamp) | ||||
| { | ||||
| 	auto it = std::find_if(basic_notifications.begin(), basic_notifications.end(), | ||||
| 		boost::bind(&NotificationData::type, _1) == type);	 | ||||
| 	if (it != basic_notifications.end()) | ||||
| 		push_notification_data( *it, canvas, timestamp); | ||||
| } | ||||
| void NotificationManager::push_notification(const std::string& text, GLCanvas3D& canvas, int timestamp) | ||||
| { | ||||
| 	push_notification_data({ NotificationType::CustomNotification, NotificationLevel::RegularNotification, 10, text }, canvas, timestamp ); | ||||
| } | ||||
| void NotificationManager::push_notification(const std::string& text, NotificationManager::NotificationLevel level, GLCanvas3D& canvas, int timestamp) | ||||
| { | ||||
| 	switch (level) | ||||
| 	{ | ||||
| 	case Slic3r::GUI::NotificationManager::NotificationLevel::RegularNotification: | ||||
| 		push_notification_data({ NotificationType::CustomNotification, level, 10, text }, canvas, timestamp); | ||||
| 		break; | ||||
| 	case Slic3r::GUI::NotificationManager::NotificationLevel::ErrorNotification: | ||||
| 		push_notification_data({ NotificationType::CustomNotification, level, 0, text }, canvas, timestamp); | ||||
| 
 | ||||
| 		break; | ||||
| 	case Slic3r::GUI::NotificationManager::NotificationLevel::ImportantNotification: | ||||
| 		push_notification_data({ NotificationType::CustomNotification, level, 0, text }, canvas, timestamp); | ||||
| 		break; | ||||
| 	default: | ||||
| 		break; | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::push_slicing_error_notification(const std::string& text, GLCanvas3D& canvas) | ||||
| { | ||||
| 	set_all_slicing_errors_gray(false); | ||||
| 	push_notification_data({ NotificationType::SlicingError, NotificationLevel::ErrorNotification, 0,  _u8L("ERROR:") + "\n" + text }, canvas, 0); | ||||
| 	close_notification_of_type(NotificationType::SlicingComplete); | ||||
| } | ||||
| void NotificationManager::push_slicing_warning_notification(const std::string& text, bool gray, GLCanvas3D& canvas, size_t oid, int warning_step) | ||||
| { | ||||
| 	NotificationData data { NotificationType::SlicingWarning, NotificationLevel::WarningNotification, 0,  _u8L("WARNING:") + "\n" + text }; | ||||
| 
 | ||||
| 	NotificationManager::SlicingWarningNotification* notification = new NotificationManager::SlicingWarningNotification(data, m_next_id++, m_evt_handler); | ||||
| 	notification->set_object_id(oid); | ||||
| 	notification->set_warning_step(warning_step); | ||||
| 	if  | ||||
| 		(push_notification_data(notification, canvas, 0)) { | ||||
| 		notification->set_gray(gray);		 | ||||
| 	} | ||||
| 	else { | ||||
| 		delete notification; | ||||
| 	} | ||||
| 	 | ||||
| } | ||||
| void NotificationManager::push_plater_error_notification(const std::string& text, GLCanvas3D& canvas) | ||||
| { | ||||
| 	push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotification, 0,  _u8L("ERROR:") + "\n" + text }, canvas, 0); | ||||
| } | ||||
| void NotificationManager::push_plater_warning_notification(const std::string& text, GLCanvas3D& canvas) | ||||
| { | ||||
| 	push_notification_data({ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0,  _u8L("WARNING:") + "\n" + text }, canvas, 0); | ||||
| } | ||||
| void NotificationManager::close_plater_error_notification() | ||||
| { | ||||
| 	for (PopNotification* notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::PlaterError) { | ||||
| 			notification->close(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::close_plater_warning_notification(const std::string& text) | ||||
| { | ||||
| 	for (PopNotification* notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::PlaterWarning && notification->compare_text(_u8L("WARNING:") + "\n" + text)) { | ||||
| 			notification->close(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::set_all_slicing_errors_gray(bool g) | ||||
| { | ||||
| 	for (PopNotification* notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::SlicingError) { | ||||
| 			notification->set_gray(g); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::set_all_slicing_warnings_gray(bool g) | ||||
| { | ||||
| 	for (PopNotification* notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::SlicingWarning) { | ||||
| 			notification->set_gray(g); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::set_slicing_warning_gray(const std::string& text, bool g) | ||||
| { | ||||
| 	for (PopNotification* notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::SlicingWarning && notification->compare_text(text)) { | ||||
| 			notification->set_gray(g); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::close_slicing_errors_and_warnings() | ||||
| { | ||||
| 	for (PopNotification* notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::SlicingError || notification->get_type() == NotificationType::SlicingWarning) { | ||||
| 			notification->close(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::push_slicing_complete_notification(GLCanvas3D& canvas, int timestamp, bool large) | ||||
| { | ||||
| 	std::string hypertext; | ||||
| 	int         time = 10; | ||||
| 	if(large) | ||||
| 	{ | ||||
| 		hypertext = _u8L("Export G-Code."); | ||||
| 		time = 0; | ||||
| 	} | ||||
| 	NotificationData data{ NotificationType::SlicingComplete, NotificationLevel::RegularNotification, time,  _u8L("Slicing finished."), hypertext }; | ||||
| 
 | ||||
| 	NotificationManager::SlicingCompleteLargeNotification* notification = new NotificationManager::SlicingCompleteLargeNotification(data, m_next_id++, m_evt_handler, large); | ||||
| 	if (push_notification_data(notification, canvas, timestamp)) { | ||||
| 	} else { | ||||
| 		delete notification; | ||||
| 	}	 | ||||
| } | ||||
| void NotificationManager::set_slicing_complete_print_time(std::string info) | ||||
| { | ||||
| 	for (PopNotification* notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::SlicingComplete) { | ||||
| 			dynamic_cast<SlicingCompleteLargeNotification*>(notification)->set_print_info(info); | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::set_slicing_complete_large(bool large) | ||||
| { | ||||
| 	for (PopNotification* notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::SlicingComplete) { | ||||
| 			dynamic_cast<SlicingCompleteLargeNotification*>(notification)->set_large(large); | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::close_notification_of_type(const NotificationType type) | ||||
| { | ||||
| 	for (PopNotification* notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == type) { | ||||
| 			notification->close(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::compare_warning_oids(const std::vector<size_t>& living_oids) | ||||
| { | ||||
| 	for (PopNotification* notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::SlicingWarning) { | ||||
| 			auto w = dynamic_cast<SlicingWarningNotification*>(notification); | ||||
| 			bool found = false; | ||||
| 			for (size_t oid : living_oids) { | ||||
| 				if (w->get_object_id() == oid) { | ||||
| 					found = true; | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
| 			if (!found) | ||||
| 				notification->close(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| bool NotificationManager::push_notification_data(const NotificationData ¬ification_data,  GLCanvas3D& canvas, int timestamp) | ||||
| { | ||||
| 	PopNotification* n = new PopNotification(notification_data, m_next_id++, m_evt_handler); | ||||
| 	bool r = push_notification_data(n, canvas, timestamp); | ||||
| 	if (!r) | ||||
| 		delete n; | ||||
| 	return r; | ||||
| } | ||||
| bool NotificationManager::push_notification_data(NotificationManager::PopNotification* notification, GLCanvas3D& canvas, int timestamp) | ||||
| { | ||||
| 	// if timestamped notif, push only new one
 | ||||
| 	if (timestamp != 0) { | ||||
| 		if (m_used_timestamps.find(timestamp) == m_used_timestamps.end()) { | ||||
| 			m_used_timestamps.insert(timestamp); | ||||
| 		} else { | ||||
| 			return false; | ||||
| 		} | ||||
| 	} | ||||
| 	if (!this->find_older(notification)) { | ||||
| 			m_pop_notifications.emplace_back(notification); | ||||
| 		canvas.request_extra_frame(); | ||||
| 		return true; | ||||
| 	} else { | ||||
| 		m_pop_notifications.back()->update(notification->get_data()); | ||||
| 		canvas.request_extra_frame(); | ||||
| 		return false; | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::render_notifications(GLCanvas3D& canvas) | ||||
| { | ||||
| 	float    last_x = 0.0f; | ||||
| 	float    current_height = 0.0f; | ||||
| 	bool     request_next_frame = false; | ||||
| 	bool     render_main = false; | ||||
| 	bool     hovered = false;	 | ||||
| 	sort_notifications(); | ||||
| 	// iterate thru notifications and render them / erease them
 | ||||
| 	for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) { | ||||
| 		if ((*it)->get_finished()) { | ||||
| 			delete (*it); | ||||
| 			it = m_pop_notifications.erase(it); | ||||
| 		} else { | ||||
| 			(*it)->set_paused(m_hovered); | ||||
| 			PopNotification::RenderResult res = (*it)->render(canvas, last_x); | ||||
| 			if (res != PopNotification::RenderResult::Finished) { | ||||
| 				last_x = (*it)->get_top() + GAP_WIDTH; | ||||
| 				current_height = std::max(current_height, (*it)->get_current_top()); | ||||
| 				render_main = true; | ||||
| 			} | ||||
| 			if (res == PopNotification::RenderResult::Countdown || res == PopNotification::RenderResult::ClosePending || res == PopNotification::RenderResult::Finished) | ||||
| 				request_next_frame = true; | ||||
| 			if (res == PopNotification::RenderResult::Hovered) | ||||
| 				hovered = true; | ||||
| 			++it; | ||||
| 		} | ||||
| 	} | ||||
| 	m_hovered = hovered; | ||||
| 
 | ||||
| 	//actualizate timers and request frame if needed
 | ||||
| 	wxWindow* p = dynamic_cast<wxWindow*> (wxGetApp().plater()); | ||||
| 	while (p->GetParent()) | ||||
| 		p = p->GetParent(); | ||||
| 	wxTopLevelWindow* top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p); | ||||
| 	if (!top_level_wnd->IsActive()) | ||||
| 		return; | ||||
| 
 | ||||
| 	if (!m_hovered && m_last_time < wxGetLocalTime()) | ||||
| 	{ | ||||
| 		if (wxGetLocalTime() - m_last_time == 1) | ||||
| 		{ | ||||
| 			for(auto notification : m_pop_notifications) | ||||
| 			{ | ||||
| 				notification->substract_remaining_time(); | ||||
| 			} | ||||
| 		} | ||||
| 		m_last_time = wxGetLocalTime(); | ||||
| 	} | ||||
| 
 | ||||
| 	if (request_next_frame) | ||||
| 		canvas.request_extra_frame(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void NotificationManager::sort_notifications() | ||||
| { | ||||
| 	std::sort(m_pop_notifications.begin(), m_pop_notifications.end(), [](PopNotification* n1, PopNotification* n2) { | ||||
| 		int n1l = (int)n1->get_data().level; | ||||
| 		int n2l = (int)n2->get_data().level; | ||||
| 		if (n1l == n2l && n1->get_is_gray() && !n2->get_is_gray()) | ||||
| 			return true; | ||||
| 		return (n1l < n2l); | ||||
| 		}); | ||||
| } | ||||
| 
 | ||||
| bool NotificationManager::find_older(NotificationManager::PopNotification* notification) | ||||
| { | ||||
| 	NotificationType type = notification->get_type(); | ||||
| 	std::string text = notification->get_data().text1; | ||||
| 	for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end(); ++it) { | ||||
| 		if((*it)->get_type() == type && !(*it)->get_finished()) { | ||||
| 			if (type == NotificationType::CustomNotification || type == NotificationType::PlaterWarning) { | ||||
| 				if (!(*it)->compare_text(text)) | ||||
| 					continue; | ||||
| 			}else if (type == NotificationType::SlicingWarning) { | ||||
| 				auto w1 = dynamic_cast<SlicingWarningNotification*>(notification); | ||||
| 				auto w2 = dynamic_cast<SlicingWarningNotification*>(*it); | ||||
| 				if (w1 != nullptr && w2 != nullptr) { | ||||
| 					if (!(*it)->compare_text(text) || w1->get_object_id() != w2->get_object_id()) { | ||||
| 						continue; | ||||
| 					} | ||||
| 				} else { | ||||
| 					continue; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if (it != m_pop_notifications.end() - 1) | ||||
| 				std::rotate(it, it + 1, m_pop_notifications.end()); | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::dpi_changed() | ||||
| { | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| }//namespace GUI
 | ||||
| }//namespace Slic3r
 | ||||
							
								
								
									
										268
									
								
								src/slic3r/GUI/NotificationManager.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								src/slic3r/GUI/NotificationManager.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,268 @@ | |||
| #ifndef slic3r_GUI_NotificationManager_hpp_ | ||||
| #define slic3r_GUI_NotificationManager_hpp_ | ||||
| 
 | ||||
| #include "Event.hpp" | ||||
| #include "I18N.hpp" | ||||
| 
 | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include <deque> | ||||
| #include <unordered_set> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
| 
 | ||||
| using EjectDriveNotificationClickedEvent = SimpleEvent; | ||||
| wxDECLARE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClickedEvent); | ||||
| using ExportGcodeNotificationClickedEvent = SimpleEvent; | ||||
| wxDECLARE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent); | ||||
| using PresetUpdateAviableClickedEvent = SimpleEvent; | ||||
| wxDECLARE_EVENT(EVT_PRESET_UPDATE_AVIABLE_CLICKED, PresetUpdateAviableClickedEvent); | ||||
| 
 | ||||
| class GLCanvas3D; | ||||
| class ImGuiWrapper; | ||||
| 
 | ||||
| enum class NotificationType | ||||
| { | ||||
| 	CustomNotification, | ||||
| 	SlicingComplete, | ||||
| 	SlicingNotPossible, | ||||
| 	ExportToRemovableFinished, | ||||
| 	Mouse3dDisconnected, | ||||
| 	Mouse3dConnected, | ||||
| 	NewPresetsAviable, | ||||
| 	NewAppAviable, | ||||
| 	PresetUpdateAviable, | ||||
| 	LoadingFailed, | ||||
| 	ValidateError, // currently not used - instead Slicing error is used for both slicing and validate errors
 | ||||
| 	SlicingError, | ||||
| 	SlicingWarning, | ||||
| 	PlaterError, | ||||
| 	PlaterWarning, | ||||
| 	ApplyError | ||||
| 
 | ||||
| }; | ||||
| class NotificationManager | ||||
| { | ||||
| public: | ||||
| 	enum class NotificationLevel : int | ||||
| 	{ | ||||
| 		ErrorNotification =     4, | ||||
| 		WarningNotification =   3, | ||||
| 		ImportantNotification = 2, | ||||
| 		RegularNotification   = 1, | ||||
| 	}; | ||||
| 	// duration 0 means not disapearing
 | ||||
| 	struct NotificationData { | ||||
| 		NotificationType    type; | ||||
| 		NotificationLevel   level; | ||||
| 		const int           duration; | ||||
| 		const std::string   text1; | ||||
| 		const std::string   hypertext = std::string(); | ||||
| 		const std::string   text2     = std::string(); | ||||
| 	}; | ||||
| 
 | ||||
| 	//Pop notification - shows only once to user.
 | ||||
| 	class PopNotification | ||||
| 	{ | ||||
| 	public: | ||||
| 		enum class RenderResult | ||||
| 		{ | ||||
| 			Finished, | ||||
| 			ClosePending, | ||||
| 			Static, | ||||
| 			Countdown, | ||||
| 			Hovered | ||||
| 		}; | ||||
| 		 PopNotification(const NotificationData &n, const int id, wxEvtHandler* evt_handler); | ||||
| 		virtual ~PopNotification(); | ||||
| 		RenderResult           render(GLCanvas3D& canvas, const float& initial_y); | ||||
| 		// close will dissapear notification on next render
 | ||||
| 		void                   close() { m_close_pending = true; } | ||||
| 		// data from newer notification of same type
 | ||||
| 		void                   update(const NotificationData& n); | ||||
| 		bool                   get_finished() const { return m_finished; } | ||||
| 		// returns top after movement
 | ||||
| 		float                  get_top() const { return m_top_y; } | ||||
| 		//returns top in actual frame
 | ||||
| 		float                  get_current_top() const { return m_top_y; } | ||||
| 		const NotificationType get_type() const { return m_data.type; } | ||||
| 		const NotificationData get_data() const { return m_data;  } | ||||
| 		const bool             get_is_gray() const { return m_is_gray; } | ||||
| 		// Call equals one second down
 | ||||
| 		void                   substract_remaining_time() { m_remaining_time--; } | ||||
| 		void                   set_gray(bool g) { m_is_gray = g; } | ||||
| 		void                   set_paused(bool p) { m_paused = p; } | ||||
| 		bool                   compare_text(const std::string& text); | ||||
| 	protected: | ||||
| 		// Call after every size change
 | ||||
| 		void         init(); | ||||
| 		// Calculetes correct size but not se it in imgui!
 | ||||
| 		virtual void set_next_window_size(ImGuiWrapper& imgui); | ||||
| 		virtual void render_text(ImGuiWrapper& imgui, | ||||
| 			                     const float win_size_x, const float win_size_y, | ||||
| 			                     const float win_pos_x , const float win_pos_y); | ||||
| 		void         render_close_button(ImGuiWrapper& imgui, | ||||
| 			                             const float win_size_x, const float win_size_y, | ||||
| 			                             const float win_pos_x , const float win_pos_y); | ||||
| 		void         render_countdown(ImGuiWrapper& imgui, | ||||
| 			                          const float win_size_x, const float win_size_y, | ||||
| 			                          const float win_pos_x , const float win_pos_y); | ||||
| 		void         render_hypertext(ImGuiWrapper& imgui, | ||||
| 			                          const float text_x, const float text_y, | ||||
| 		                              const std::string text, | ||||
| 		                              bool more = false); | ||||
| 		void         render_left_sign(ImGuiWrapper& imgui); | ||||
| 		void         render_minimize_button(ImGuiWrapper& imgui, | ||||
| 			                                const float win_pos_x, const float win_pos_y); | ||||
| 		void         on_text_click(); | ||||
| 
 | ||||
| 		const NotificationData m_data; | ||||
| 
 | ||||
| 		int              m_id; | ||||
| 		// Main text
 | ||||
| 		std::string      m_text1; | ||||
| 		// Clickable text
 | ||||
| 		std::string      m_hypertext; | ||||
| 		// Aditional text after hypertext - currently not used
 | ||||
| 		std::string      m_text2; | ||||
| 		// Countdown variables
 | ||||
| 		long             m_remaining_time; | ||||
| 		bool             m_counting_down; | ||||
| 		long             m_last_remaining_time; | ||||
| 		bool             m_paused{ false }; | ||||
| 		int              m_countdown_frame{ 0 }; | ||||
| 		bool             m_fading_out{ false }; | ||||
| 		// total time left when fading beggins
 | ||||
| 		float            m_fading_time{ 0.0f };  | ||||
| 		float            m_current_fade_opacity{ 1.f }; | ||||
| 		// If hidden the notif is alive but not visible to user
 | ||||
| 		bool             m_hidden               { false }; | ||||
| 		//  m_finished = true - does not render, marked to delete
 | ||||
| 		bool             m_finished             { false };  | ||||
| 		// Will go to m_finished next render
 | ||||
| 		bool             m_close_pending        { false };  | ||||
| 		// variables to count positions correctly
 | ||||
| 		float            m_window_width_offset; | ||||
| 		float            m_left_indentation; | ||||
| 		// Total size of notification window - varies based on monitor
 | ||||
| 		float            m_window_height        { 56.0f };   | ||||
| 		float            m_window_width         { 450.0f }; | ||||
| 		//Distance from bottom of notifications to top of this notification
 | ||||
| 		float            m_top_y                { 0.0f };   | ||||
| 		 | ||||
| 		// Height of text
 | ||||
| 		// Used as basic scaling unit!
 | ||||
| 		float            m_line_height; | ||||
| 		std::vector<int> m_endlines; | ||||
| 		// Gray are f.e. eorrors when its uknown if they are still valid
 | ||||
| 		bool             m_is_gray              { false }; | ||||
| 		//if multiline = true, notification is showing all lines(>2)
 | ||||
| 		bool             m_multiline            { false }; | ||||
| 		int              m_lines_count{ 1 }; | ||||
| 		wxEvtHandler*    m_evt_handler; | ||||
| 	}; | ||||
| 
 | ||||
| 	class SlicingCompleteLargeNotification : public PopNotification | ||||
| 	{ | ||||
| 	public: | ||||
| 		SlicingCompleteLargeNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler, bool largeds); | ||||
| 		void set_large(bool l); | ||||
| 		bool get_large() { return m_is_large; } | ||||
| 
 | ||||
| 		void set_print_info(std::string info); | ||||
| 	protected: | ||||
| 		virtual void render_text(ImGuiWrapper& imgui, | ||||
| 			                     const float win_size_x, const float win_size_y, | ||||
| 			                     const float win_pos_x, const float win_pos_y)  | ||||
| 			                     override; | ||||
| 
 | ||||
| 		bool        m_is_large; | ||||
| 		bool        m_has_print_info { false }; | ||||
| 		std::string m_print_info { std::string() }; | ||||
| 	}; | ||||
| 
 | ||||
| 	class SlicingWarningNotification : public PopNotification | ||||
| 	{ | ||||
| 	public: | ||||
| 		SlicingWarningNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler) : PopNotification(n, id, evt_handler) {} | ||||
| 		void         set_object_id(size_t id) { object_id = id; } | ||||
| 		const size_t get_object_id() { return object_id; } | ||||
| 		void         set_warning_step(int ws) { warning_step = ws; } | ||||
| 		const int    get_warning_step() { return warning_step; } | ||||
| 	protected: | ||||
| 		size_t object_id; | ||||
| 		int    warning_step; | ||||
| 	}; | ||||
| 
 | ||||
| 	NotificationManager(wxEvtHandler* evt_handler); | ||||
| 	~NotificationManager(); | ||||
| 
 | ||||
| 	 | ||||
| 	// only type means one of basic_notification (see below)
 | ||||
| 	void push_notification(const NotificationType type, GLCanvas3D& canvas, int timestamp = 0); | ||||
| 	// only text means Undefined type
 | ||||
| 	void push_notification(const std::string& text, GLCanvas3D& canvas, int timestamp = 0); | ||||
| 	void push_notification(const std::string& text, NotificationLevel level, GLCanvas3D& canvas, int timestamp = 0); | ||||
| 	// creates Slicing Error notification with custom text
 | ||||
| 	void push_slicing_error_notification(const std::string& text, GLCanvas3D& canvas); | ||||
| 	// creates Slicing Warning notification with custom text
 | ||||
| 	void push_slicing_warning_notification(const std::string& text, bool gray, GLCanvas3D& canvas, size_t oid, int warning_step); | ||||
| 	// marks slicing errors as gray
 | ||||
| 	void set_all_slicing_errors_gray(bool g); | ||||
| 	// marks slicing warings as gray
 | ||||
| 	void set_all_slicing_warnings_gray(bool g); | ||||
| 	void set_slicing_warning_gray(const std::string& text, bool g); | ||||
| 	// imidietly stops showing slicing errors
 | ||||
| 	void close_slicing_errors_and_warnings(); | ||||
| 	void compare_warning_oids(const std::vector<size_t>& living_oids); | ||||
| 	void push_plater_error_notification(const std::string& text, GLCanvas3D& canvas); | ||||
| 	void push_plater_warning_notification(const std::string& text, GLCanvas3D& canvas); | ||||
| 	void close_plater_error_notification(); | ||||
| 	void close_plater_warning_notification(const std::string& text); | ||||
| 	// creates special notification slicing complete
 | ||||
| 	// if large = true prints printing time and export button 
 | ||||
| 	void push_slicing_complete_notification(GLCanvas3D& canvas, int timestamp, bool large); | ||||
| 	void set_slicing_complete_print_time(std::string info); | ||||
| 	void set_slicing_complete_large(bool large); | ||||
| 	// renders notifications in queue and deletes expired ones
 | ||||
| 	void render_notifications(GLCanvas3D& canvas); | ||||
| 	// finds and closes all notifications of given type
 | ||||
| 	void close_notification_of_type(const NotificationType type); | ||||
| 	void dpi_changed(); | ||||
| private: | ||||
| 	//pushes notification into the queue of notifications that are rendered
 | ||||
| 	//can be used to create custom notification
 | ||||
| 	bool push_notification_data(const NotificationData& notification_data, GLCanvas3D& canvas, int timestamp); | ||||
| 	bool push_notification_data(NotificationManager::PopNotification* notification, GLCanvas3D& canvas, int timestamp); | ||||
| 	//finds older notification of same type and moves it to the end of queue. returns true if found
 | ||||
| 	bool find_older(NotificationManager::PopNotification* notification); | ||||
| 	void sort_notifications(); | ||||
| 
 | ||||
| 	wxEvtHandler*                m_evt_handler; | ||||
| 	std::deque<PopNotification*> m_pop_notifications; | ||||
| 	int                          m_next_id { 1 }; | ||||
| 	long                         m_last_time { 0 }; | ||||
| 	bool                         m_hovered { false }; | ||||
| 	//timestamps used for slining finished - notification could be gone so it needs to be stored here
 | ||||
| 	std::unordered_set<int>      m_used_timestamps; | ||||
| 
 | ||||
| 	//prepared (basic) notifications
 | ||||
| 	const std::vector<NotificationData> basic_notifications = { | ||||
| 		{NotificationType::SlicingNotPossible, NotificationLevel::RegularNotification, 10,  _u8L("Slicing is not possible.")}, | ||||
| 		{NotificationType::ExportToRemovableFinished, NotificationLevel::ImportantNotification, 0,  _u8L("Exporting finished."),  _u8L("Eject drive.") }, | ||||
| 		{NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10,  _u8L("3D Mouse disconnected.") }, | ||||
| 		{NotificationType::Mouse3dConnected, NotificationLevel::RegularNotification, 5,  _u8L("3D Mouse connected.") }, | ||||
| 		{NotificationType::NewPresetsAviable, NotificationLevel::ImportantNotification, 20,  _u8L("New Presets are available."),  _u8L("See here.") }, | ||||
| 		{NotificationType::PresetUpdateAviable, NotificationLevel::ImportantNotification, 20,  _u8L("Configuration update is available."),  _u8L("See more.")}, | ||||
| 		{NotificationType::NewAppAviable, NotificationLevel::ImportantNotification, 20,  _u8L("New version is available."),  _u8L("See Releases page.")}, | ||||
| 		//{NotificationType::NewAppAviable, NotificationLevel::ImportantNotification, 20,  _u8L("New vesion of PrusaSlicer is available.",  _u8L("Download page.") },
 | ||||
| 		//{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20,  _u8L("Loading of model has Failed") },
 | ||||
| 		//{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10,  _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification
 | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| }//namespace GUI
 | ||||
| }//namespace Slic3r
 | ||||
| 
 | ||||
| #endif //slic3r_GUI_NotificationManager_hpp_
 | ||||
|  | @ -79,8 +79,10 @@ | |||
| #include "../Utils/PrintHost.hpp" | ||||
| #include "../Utils/FixModelByWin10.hpp" | ||||
| #include "../Utils/UndoRedo.hpp" | ||||
| #include "../Utils/PresetUpdater.hpp" | ||||
| #include "RemovableDriveManager.hpp" | ||||
| #include "InstanceCheck.hpp" | ||||
| #include "NotificationManager.hpp" | ||||
| 
 | ||||
| #ifdef __APPLE__ | ||||
| #include "Gizmos/GLGizmosManager.hpp" | ||||
|  | @ -106,6 +108,7 @@ wxDEFINE_EVENT(EVT_SCHEDULE_BACKGROUND_PROCESS,     SimpleEvent); | |||
| wxDEFINE_EVENT(EVT_SLICING_UPDATE,                  SlicingStatusEvent); | ||||
| wxDEFINE_EVENT(EVT_SLICING_COMPLETED,               wxCommandEvent); | ||||
| wxDEFINE_EVENT(EVT_PROCESS_COMPLETED,               wxCommandEvent); | ||||
| wxDEFINE_EVENT(EVT_EXPORT_BEGAN,                    wxCommandEvent); | ||||
| 
 | ||||
| // Sidebar widgets
 | ||||
| 
 | ||||
|  | @ -720,7 +723,7 @@ struct Sidebar::priv | |||
|     wxButton *btn_export_gcode; | ||||
|     wxButton *btn_reslice; | ||||
|     ScalableButton *btn_send_gcode; | ||||
|     ScalableButton *btn_remove_device; | ||||
|     ScalableButton *btn_eject_device; | ||||
| 	ScalableButton* btn_export_gcode_removable; //exports to removable drives (appears only if removable drive is connected)
 | ||||
| 
 | ||||
|     bool                is_collapsed {false}; | ||||
|  | @ -893,12 +896,12 @@ Sidebar::Sidebar(Plater *parent) | |||
|     }; | ||||
| 
 | ||||
|     init_scalable_btn(&p->btn_send_gcode   , "export_gcode", _L("Send to printer") + "\tCtrl+Shift+G"); | ||||
|     init_scalable_btn(&p->btn_remove_device, "eject_sd"       , _L("Remove device") + "\tCtrl+T"); | ||||
|     init_scalable_btn(&p->btn_eject_device, "eject_sd"       , _L("Remove device") + "\tCtrl+T"); | ||||
| 	init_scalable_btn(&p->btn_export_gcode_removable, "export_to_sd", _L("Export to SD card / Flash drive") + "\tCtrl+U"); | ||||
| 
 | ||||
|     // regular buttons "Slice now" and "Export G-code" 
 | ||||
| 
 | ||||
|     const int scaled_height = p->btn_remove_device->GetBitmapHeight() + 4; | ||||
|     const int scaled_height = p->btn_eject_device->GetBitmapHeight() + 4; | ||||
|     auto init_btn = [this](wxButton **btn, wxString label, const int button_height) { | ||||
|         *btn = new wxButton(this, wxID_ANY, label, wxDefaultPosition, | ||||
|                             wxSize(-1, button_height), wxBU_EXACTFIT); | ||||
|  | @ -916,7 +919,7 @@ Sidebar::Sidebar(Plater *parent) | |||
|     complect_btns_sizer->Add(p->btn_export_gcode, 1, wxEXPAND); | ||||
|     complect_btns_sizer->Add(p->btn_send_gcode); | ||||
| 	complect_btns_sizer->Add(p->btn_export_gcode_removable); | ||||
|     complect_btns_sizer->Add(p->btn_remove_device); | ||||
|     complect_btns_sizer->Add(p->btn_eject_device); | ||||
| 	 | ||||
| 
 | ||||
|     btns_sizer->Add(p->btn_reslice, 0, wxEXPAND | wxTOP, margin_5); | ||||
|  | @ -939,7 +942,7 @@ Sidebar::Sidebar(Plater *parent) | |||
|         p->plater->select_view_3D("Preview"); | ||||
|     }); | ||||
|     p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); }); | ||||
|     p->btn_remove_device->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->eject_drive(); }); | ||||
|     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); }); | ||||
| } | ||||
| 
 | ||||
|  | @ -1087,9 +1090,9 @@ void Sidebar::msw_rescale() | |||
|     p->object_info->msw_rescale(); | ||||
| 
 | ||||
|     p->btn_send_gcode->msw_rescale(); | ||||
|     p->btn_remove_device->msw_rescale(); | ||||
|     p->btn_eject_device->msw_rescale(); | ||||
| 	p->btn_export_gcode_removable->msw_rescale(); | ||||
|     const int scaled_height = p->btn_remove_device->GetBitmap().GetHeight() + 4; | ||||
|     const int scaled_height = p->btn_eject_device->GetBitmap().GetHeight() + 4; | ||||
|     p->btn_export_gcode->SetMinSize(wxSize(-1, scaled_height)); | ||||
|     p->btn_reslice     ->SetMinSize(wxSize(-1, scaled_height)); | ||||
| 
 | ||||
|  | @ -1118,7 +1121,7 @@ void Sidebar::sys_color_changed() | |||
| 
 | ||||
|     // btn...->msw_rescale() updates icon on button, so use it
 | ||||
|     p->btn_send_gcode->msw_rescale(); | ||||
|     p->btn_remove_device->msw_rescale(); | ||||
|     p->btn_eject_device->msw_rescale(); | ||||
|     p->btn_export_gcode_removable->msw_rescale(); | ||||
| 
 | ||||
|     p->scrolled->Layout(); | ||||
|  | @ -1402,6 +1405,12 @@ void Sidebar::update_sliced_info_sizer() | |||
|                     new_label += format_wxstr("\n   - %1%", _L("normal mode")); | ||||
|                     info_text += format_wxstr("\n%1%", ps.estimated_normal_print_time); | ||||
|                     fill_labels(ps.estimated_normal_custom_gcode_print_times, new_label, info_text); | ||||
| 
 | ||||
| 					// uncomment next line to not disappear slicing finished notif when colapsing sidebar before time estimate
 | ||||
| 					//if (p->plater->is_sidebar_collapsed())
 | ||||
| 					p->plater->get_notification_manager()->set_slicing_complete_large(p->plater->is_sidebar_collapsed()); | ||||
| 					p->plater->get_notification_manager()->set_slicing_complete_print_time("Estimated printing time: " + ps.estimated_normal_print_time); | ||||
| 
 | ||||
|                 } | ||||
|                 if (ps.estimated_silent_print_time != "N/A") { | ||||
|                     new_label += format_wxstr("\n   - %1%", _L("stealth mode")); | ||||
|  | @ -1441,15 +1450,16 @@ void Sidebar::enable_buttons(bool enable) | |||
|     p->btn_reslice->Enable(enable); | ||||
|     p->btn_export_gcode->Enable(enable); | ||||
|     p->btn_send_gcode->Enable(enable); | ||||
|     p->btn_remove_device->Enable(enable); | ||||
|     p->btn_eject_device->Enable(enable); | ||||
| 	p->btn_export_gcode_removable->Enable(enable); | ||||
| } | ||||
| 
 | ||||
| bool Sidebar::show_reslice(bool show)         const { return p->btn_reslice->Show(show); } | ||||
| bool Sidebar::show_export(bool show)          const { return p->btn_export_gcode->Show(show); } | ||||
| bool Sidebar::show_send(bool show)            const { return p->btn_send_gcode->Show(show); } | ||||
| bool Sidebar::show_disconnect(bool show)      const { return p->btn_remove_device->Show(show); } | ||||
| bool Sidebar::show_export_removable(bool show)const { return p->btn_export_gcode_removable->Show(show); } | ||||
| bool Sidebar::show_reslice(bool show)          const { return p->btn_reslice->Show(show); } | ||||
| bool Sidebar::show_export(bool show)           const { return p->btn_export_gcode->Show(show); } | ||||
| bool Sidebar::show_send(bool show)             const { return p->btn_send_gcode->Show(show); } | ||||
| bool Sidebar::show_export_removable(bool show) const { return p->btn_export_gcode_removable->Show(show); } | ||||
| bool Sidebar::show_eject(bool show)            const { return p->btn_eject_device->Show(show); } | ||||
| bool Sidebar::get_eject_shown()                const { return p->btn_eject_device->IsShown(); } | ||||
| 
 | ||||
| bool Sidebar::is_multifilament() | ||||
| { | ||||
|  | @ -1651,6 +1661,7 @@ struct Plater::priv | |||
|     GLToolbar view_toolbar; | ||||
|     GLToolbar collapse_toolbar; | ||||
|     Preview *preview; | ||||
| 	NotificationManager* notification_manager; | ||||
| 
 | ||||
|     BackgroundSlicingProcess    background_process; | ||||
|     bool suppressed_backround_processing_update { false }; | ||||
|  | @ -1844,7 +1855,17 @@ struct Plater::priv | |||
|     void on_slicing_update(SlicingStatusEvent&); | ||||
|     void on_slicing_completed(wxCommandEvent&); | ||||
|     void on_process_completed(wxCommandEvent&); | ||||
| 	void on_export_began(wxCommandEvent&); | ||||
|     void on_layer_editing_toggled(bool enable); | ||||
| 	void on_slicing_began(); | ||||
| 
 | ||||
| 	void clear_warnings(); | ||||
| 	void add_warning(const Slic3r::PrintStateBase::Warning &warning, size_t oid); | ||||
| 	void actualizate_warnings(const Model& model, size_t print_oid); | ||||
| 	// Displays dialog window with list of warnings. 
 | ||||
| 	// Returns true if user clicks OK.
 | ||||
| 	// Returns true if current_warnings vector is empty without showning the dialog
 | ||||
| 	bool warnings_dialog(); | ||||
| 
 | ||||
|     void on_action_add(SimpleEvent&); | ||||
|     void on_action_split_objects(SimpleEvent&); | ||||
|  | @ -1895,7 +1916,7 @@ struct Plater::priv | |||
|     // Flag indicating that the G-code export targets a removable device, therefore the show_action_buttons() needs to be called at any case when the background processing finishes.
 | ||||
|     bool 						writing_to_removable_device = { false }; | ||||
|     bool                        inside_snapshot_capture() { return m_prevent_snapshots != 0; } | ||||
| 
 | ||||
| 	bool                        process_completed_with_error { false }; | ||||
| private: | ||||
|     bool init_object_menu(); | ||||
|     bool init_common_menu(wxMenu* menu, const bool is_part = false); | ||||
|  | @ -1923,6 +1944,11 @@ private: | |||
|                                                               * */ | ||||
|     std::string 				m_last_fff_printer_profile_name; | ||||
|     std::string 				m_last_sla_printer_profile_name; | ||||
| 
 | ||||
| 	// vector of all warnings generated by last slicing
 | ||||
| 	std::vector<std::pair<Slic3r::PrintStateBase::Warning, size_t>> current_warnings; | ||||
| 	bool show_warning_dialog { false }; | ||||
| 	 | ||||
| }; | ||||
| 
 | ||||
| const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase); | ||||
|  | @ -1972,6 +1998,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
|         }); | ||||
|     background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED); | ||||
|     background_process.set_finished_event(EVT_PROCESS_COMPLETED); | ||||
| 	background_process.set_export_began_event(EVT_EXPORT_BEGAN); | ||||
|     // Default printer technology for default config.
 | ||||
|     background_process.select_technology(this->printer_technology); | ||||
|     // Register progress callback from the Print class to the Plater.
 | ||||
|  | @ -2082,8 +2109,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
|     preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_double_slider(evt); }); | ||||
| #endif // ENABLE_GCODE_VIEWER
 | ||||
| 
 | ||||
|     q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this); | ||||
| 	q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this); | ||||
|     q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this); | ||||
| 	q->Bind(EVT_EXPORT_BEGAN, &priv::on_export_began, this); | ||||
|     q->Bind(EVT_GLVIEWTOOLBAR_3D, [q](SimpleEvent&) { q->select_view_3D("3D"); }); | ||||
|     q->Bind(EVT_GLVIEWTOOLBAR_PREVIEW, [q](SimpleEvent&) { q->select_view_3D("Preview"); }); | ||||
| 
 | ||||
|  | @ -2110,16 +2138,27 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
|     }); | ||||
| #endif /* _WIN32 */ | ||||
| 
 | ||||
|     this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this](RemovableDriveEjectEvent &evt) { | ||||
| 	notification_manager = new NotificationManager(this->q); | ||||
| 	this->q->Bind(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, [this](EjectDriveNotificationClickedEvent&) { this->q->eject_drive(); }); | ||||
| 	this->q->Bind(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, [this](ExportGcodeNotificationClickedEvent&) { this->q->export_gcode(true); }); | ||||
| 	this->q->Bind(EVT_PRESET_UPDATE_AVIABLE_CLICKED, [this](PresetUpdateAviableClickedEvent&) {  wxGetApp().get_preset_updater()->on_update_notification_confirm(); }); | ||||
| 
 | ||||
| 	this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this, q](RemovableDriveEjectEvent &evt) { | ||||
| 		if (evt.data.second) { | ||||
| 			this->show_action_buttons(this->ready_to_slice); | ||||
| 			Slic3r::GUI::show_info(this->q, format_wxstr(_L("Unmounting successful. The device %s(%s) can now be safely removed from the computer."), | ||||
| 				evt.data.first.name, evt.data.first.path)); | ||||
| 		} else | ||||
| 			Slic3r::GUI::show_info(this->q, format_wxstr(_L("Ejecting of device %s(%s) has failed."), | ||||
| 				evt.data.first.name, evt.data.first.path)); | ||||
| 			notification_manager->push_notification(format(_L("Unmounting successful. The device %s(%s) can now be safely removed from the computer."),evt.data.first.name, evt.data.first.path), | ||||
| 				                                    NotificationManager::NotificationLevel::RegularNotification, *q->get_current_canvas3D()); | ||||
| 		} else { | ||||
| 			notification_manager->push_notification(format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path), | ||||
| 				                                    NotificationManager::NotificationLevel::ErrorNotification, *q->get_current_canvas3D()); | ||||
| 		} | ||||
| 	}); | ||||
|     this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this, q](RemovableDrivesChangedEvent &) { | ||||
| 		this->show_action_buttons(this->ready_to_slice);  | ||||
| 		if (!this->sidebar->get_eject_shown()) { | ||||
| 			notification_manager->close_notification_of_type(NotificationType::ExportToRemovableFinished); | ||||
| 		} | ||||
| 	}); | ||||
|     this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this](RemovableDrivesChangedEvent &) { this->show_action_buttons(this->ready_to_slice); }); | ||||
|     // Start the background thread and register this window as a target for update events.
 | ||||
|     wxGetApp().removable_drive_manager()->init(this->q); | ||||
| #ifdef _WIN32 | ||||
|  | @ -2753,6 +2792,8 @@ void Plater::priv::reset() | |||
| { | ||||
|     Plater::TakeSnapshot snapshot(q, _L("Reset Project")); | ||||
| 
 | ||||
| 	clear_warnings(); | ||||
| 
 | ||||
|     set_project_filename(wxEmptyString); | ||||
| 
 | ||||
| #if !ENABLE_GCODE_VIEWER | ||||
|  | @ -2938,22 +2979,13 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool | |||
| 		// The state of the Print changed, and it is non-zero. Let's validate it and give the user feedback on errors.
 | ||||
|         std::string err = this->background_process.validate(); | ||||
|         if (err.empty()) { | ||||
| 			notification_manager->set_all_slicing_errors_gray(true); | ||||
|             if (invalidated != Print::APPLY_STATUS_UNCHANGED && this->background_processing_enabled()) | ||||
|                 return_state |= UPDATE_BACKGROUND_PROCESS_RESTART; | ||||
|         } else { | ||||
|             // The print is not valid.
 | ||||
|             // Only show the error message immediately, if the top level parent of this window is active.
 | ||||
|             auto p = dynamic_cast<wxWindow*>(this->q); | ||||
|             while (p->GetParent()) | ||||
|                 p = p->GetParent(); | ||||
|             auto *top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p); | ||||
|             if (! postpone_error_messages && top_level_wnd && top_level_wnd->IsActive()) { | ||||
|                 // The error returned from the Print needs to be translated into the local language.
 | ||||
|                 GUI::show_error(this->q, err); | ||||
|             } else { | ||||
|                 // Show the error message once the main window gets activated.
 | ||||
|                 this->delayed_error_message = err; | ||||
|             } | ||||
| 			// The print is not valid.
 | ||||
| 			// Show error as notification.
 | ||||
| 			notification_manager->push_slicing_error_notification(err, *q->get_current_canvas3D()); | ||||
|             return_state |= UPDATE_BACKGROUND_PROCESS_INVALID; | ||||
|         } | ||||
|     } else if (! this->delayed_error_message.empty()) { | ||||
|  | @ -2961,6 +2993,14 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool | |||
|         return_state |= UPDATE_BACKGROUND_PROCESS_INVALID; | ||||
|     } | ||||
| 
 | ||||
| 	//actualizate warnings
 | ||||
| 	if (invalidated != Print::APPLY_STATUS_UNCHANGED) { | ||||
| 		actualizate_warnings(this->q->model(), this->background_process.current_print()->id().id); | ||||
| 		notification_manager->set_all_slicing_warnings_gray(true); | ||||
| 		show_warning_dialog = false; | ||||
| 		process_completed_with_error = false; | ||||
| 	} | ||||
| 
 | ||||
|     if (invalidated != Print::APPLY_STATUS_UNCHANGED && was_running && ! this->background_process.running() && | ||||
|         (return_state & UPDATE_BACKGROUND_PROCESS_RESTART) == 0) { | ||||
|         // The background processing was killed and it will not be restarted.
 | ||||
|  | @ -3023,6 +3063,8 @@ bool Plater::priv::restart_background_process(unsigned int state) | |||
|                 this->statusbar()->set_status_text(_L("Cancelling")); | ||||
|                 this->background_process.stop(); | ||||
|             }); | ||||
| 			if (!show_warning_dialog) | ||||
| 				on_slicing_began(); | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
|  | @ -3049,6 +3091,7 @@ void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_remova | |||
|     if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0) | ||||
|         return; | ||||
| 
 | ||||
| 	show_warning_dialog = true; | ||||
|     if (! output_path.empty()) { | ||||
|         background_process.schedule_export(output_path.string(), output_path_on_removable_media); | ||||
|     } else { | ||||
|  | @ -3527,11 +3570,20 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) | |||
|                 state = print_object->step_state_with_warnings(static_cast<SLAPrintObjectStep>(warning_step)); | ||||
|         } | ||||
|         // Now process state.warnings.
 | ||||
| 		for (auto const& warning : state.warnings) { | ||||
| 			if (warning.current) { | ||||
| 				notification_manager->push_slicing_warning_notification(warning.message, false, *q->get_current_canvas3D(), object_id.id, warning_step); | ||||
| 				add_warning(warning, object_id.id); | ||||
| 			} | ||||
| 		} | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::on_slicing_completed(wxCommandEvent &) | ||||
| void Plater::priv::on_slicing_completed(wxCommandEvent & evt) | ||||
| { | ||||
| 	//notification_manager->push_notification(NotificationType::SlicingComplete, *q->get_current_canvas3D(), evt.GetInt());
 | ||||
| 	notification_manager->push_slicing_complete_notification(*q->get_current_canvas3D(), evt.GetInt(), is_sidebar_collapsed()); | ||||
| 
 | ||||
|     switch (this->printer_technology) { | ||||
|     case ptFFF: | ||||
|         this->update_fff_scene(); | ||||
|  | @ -3544,8 +3596,63 @@ void Plater::priv::on_slicing_completed(wxCommandEvent &) | |||
|         break; | ||||
|     default: break; | ||||
|     } | ||||
| }  | ||||
| 
 | ||||
| } | ||||
| void Plater::priv::on_export_began(wxCommandEvent& evt) | ||||
| { | ||||
| 	if (show_warning_dialog) | ||||
| 		warnings_dialog(); | ||||
| } | ||||
| void Plater::priv::on_slicing_began() | ||||
| { | ||||
| 	clear_warnings(); | ||||
| 	notification_manager->close_notification_of_type(NotificationType::SlicingComplete); | ||||
| } | ||||
| void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, size_t oid) | ||||
| { | ||||
| 	for (auto const& it : current_warnings) { | ||||
| 		if (warning.message_id == it.first.message_id) { | ||||
| 			if (warning.message_id != 0 || (warning.message_id == 0 && warning.message == it.first.message)) | ||||
| 				return; | ||||
| 		}  | ||||
| 	} | ||||
| 	current_warnings.emplace_back(std::pair<Slic3r::PrintStateBase::Warning, size_t>(warning, oid)); | ||||
| } | ||||
| void Plater::priv::actualizate_warnings(const Model& model, size_t print_oid) | ||||
| { | ||||
| 	std::vector<size_t> living_oids; | ||||
| 	living_oids.push_back(model.id().id); | ||||
| 	living_oids.push_back(print_oid); | ||||
| 	for (auto it = model.objects.begin(); it != model.objects.end(); ++it) { | ||||
| 		living_oids.push_back((*it)->id().id); | ||||
| 	} | ||||
| 	notification_manager->compare_warning_oids(living_oids); | ||||
| } | ||||
| void Plater::priv::clear_warnings() | ||||
| { | ||||
| 	notification_manager->close_slicing_errors_and_warnings(); | ||||
| 	this->current_warnings.clear(); | ||||
| } | ||||
| bool Plater::priv::warnings_dialog() | ||||
| { | ||||
| 	if (current_warnings.empty()) | ||||
| 		return true; | ||||
| 	std::string text = _u8L("There are active warnings concerning sliced models:\n"); | ||||
| 	bool empt = true; | ||||
| 	for (auto const& it : current_warnings) { | ||||
| 		int next_n = it.first.message.find_first_of('\n', 0); | ||||
| 		text += "\n"; | ||||
| 		if (next_n != std::string::npos) | ||||
| 			text += it.first.message.substr(0, next_n); | ||||
| 		else | ||||
| 			text += it.first.message; | ||||
| 	} | ||||
| 	//text += "\n\nDo you still wish to export?";
 | ||||
| 	wxMessageDialog msg_wingow(this->q, text, wxString(SLIC3R_APP_NAME " ") + _L("generated warnings"), wxOK); | ||||
| 	const auto res = msg_wingow.ShowModal(); | ||||
| 	return res == wxID_OK; | ||||
| 
 | ||||
| } | ||||
| void Plater::priv::on_process_completed(wxCommandEvent &evt) | ||||
| { | ||||
|     // Stop the background task, wait until the thread goes into the "Idle" state.
 | ||||
|  | @ -3564,14 +3671,13 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) | |||
|     if (error) { | ||||
|         wxString message = evt.GetString(); | ||||
|         if (message.IsEmpty()) | ||||
|             message = _L("Export failed"); | ||||
|         if (q->m_tracking_popup_menu) | ||||
|         	// We don't want to pop-up a message box when tracking a pop-up menu.
 | ||||
|         	// We postpone the error message instead.
 | ||||
|             q->m_tracking_popup_menu_error_message = message; | ||||
|         else | ||||
| 	        show_error(q, message); | ||||
|             message = _L("Export failed."); | ||||
| 		notification_manager->push_slicing_error_notification(boost::nowide::narrow(message), *q->get_current_canvas3D()); | ||||
|         this->statusbar()->set_status_text(message); | ||||
| 		const wxString invalid_str = _L("Invalid data"); | ||||
| 		for (auto btn : { ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport }) | ||||
| 			sidebar->set_btn_label(btn, invalid_str); | ||||
| 		process_completed_with_error = true; | ||||
|     } | ||||
|     if (canceled) | ||||
|         this->statusbar()->set_status_text(_L("Cancelled")); | ||||
|  | @ -3597,18 +3703,21 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) | |||
|     default: break; | ||||
|     } | ||||
| 	 | ||||
| 
 | ||||
|     if (canceled) { | ||||
|         if (wxGetApp().get_mode() == comSimple) | ||||
|             sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now"); | ||||
|         show_action_buttons(true); | ||||
|     } | ||||
|     else if (this->writing_to_removable_device || wxGetApp().get_mode() == comSimple) | ||||
|     else if (wxGetApp().get_mode() == comSimple) | ||||
| 	{ | ||||
| 		wxGetApp().removable_drive_manager()->set_exporting_finished(true); | ||||
| 		show_action_buttons(false); | ||||
| 	} | ||||
|     this->writing_to_removable_device = false; | ||||
| 	else if (this->writing_to_removable_device) | ||||
| 	{ | ||||
| 		show_action_buttons(false); | ||||
| 		notification_manager->push_notification(NotificationType::ExportToRemovableFinished, *q->get_current_canvas3D()); | ||||
| 	} | ||||
| 	this->writing_to_removable_device = false; | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::on_layer_editing_toggled(bool enable) | ||||
|  | @ -4268,7 +4377,7 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const | |||
| 			sidebar->show_export(true) | | ||||
| 			sidebar->show_send(send_gcode_shown) | | ||||
| 			sidebar->show_export_removable(removable_media_status.has_removable_drives) | | ||||
| 			sidebar->show_disconnect(removable_media_status.has_eject)) | ||||
| 			sidebar->show_eject(removable_media_status.has_eject)) | ||||
|             sidebar->Layout(); | ||||
|     } | ||||
|     else | ||||
|  | @ -4280,7 +4389,7 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const | |||
|             sidebar->show_export(!ready_to_slice) | | ||||
|             sidebar->show_send(send_gcode_shown && !ready_to_slice) | | ||||
| 			sidebar->show_export_removable(!ready_to_slice && removable_media_status.has_removable_drives) | | ||||
|             sidebar->show_disconnect(!ready_to_slice && removable_media_status.has_eject)) | ||||
|             sidebar->show_eject(!ready_to_slice && removable_media_status.has_eject)) | ||||
|             sidebar->Layout(); | ||||
|     } | ||||
| } | ||||
|  | @ -4881,6 +4990,9 @@ void Plater::export_gcode(bool prefer_removable) | |||
|     if (p->model.objects.empty()) | ||||
|         return; | ||||
| 
 | ||||
| 	if (p->process_completed_with_error)//here
 | ||||
| 		return; | ||||
| 
 | ||||
|     // If possible, remove accents from accented latin characters.
 | ||||
|     // This function is useful for generating file names to be processed by legacy firmwares.
 | ||||
|     fs::path default_output_file; | ||||
|  | @ -5140,7 +5252,6 @@ void Plater::export_toolpaths_to_obj() const | |||
|     p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str()); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void Plater::reslice() | ||||
| { | ||||
|     // Stop arrange and (or) optimize rotation tasks.
 | ||||
|  | @ -5885,6 +5996,16 @@ Mouse3DController& Plater::get_mouse3d_controller() | |||
|     return p->mouse3d_controller; | ||||
| } | ||||
| 
 | ||||
| const NotificationManager* Plater::get_notification_manager() const | ||||
| { | ||||
| 	return p->notification_manager; | ||||
| } | ||||
| 
 | ||||
| NotificationManager* Plater::get_notification_manager() | ||||
| { | ||||
| 	return p->notification_manager; | ||||
| } | ||||
| 
 | ||||
| bool Plater::can_delete() const { return p->can_delete(); } | ||||
| bool Plater::can_delete_all() const { return p->can_delete_all(); } | ||||
| bool Plater::can_increase_instances() const { return p->can_increase_instances(); } | ||||
|  |  | |||
|  | @ -47,6 +47,7 @@ class ObjectLayers; | |||
| class ObjectList; | ||||
| class GLCanvas3D; | ||||
| class Mouse3DController; | ||||
| class NotificationManager; | ||||
| struct Camera; | ||||
| class Bed3D; | ||||
| class GLToolbar; | ||||
|  | @ -130,8 +131,9 @@ public: | |||
|     bool                    show_reslice(bool show) const; | ||||
| 	bool                    show_export(bool show) const; | ||||
| 	bool                    show_send(bool show) const; | ||||
|     bool                    show_disconnect(bool show)const; | ||||
|     bool                    show_eject(bool show)const; | ||||
| 	bool                    show_export_removable(bool show) const; | ||||
| 	bool                    get_eject_shown() const; | ||||
|     bool                    is_multifilament(); | ||||
|     void                    update_mode(); | ||||
|     bool                    is_collapsed(); | ||||
|  | @ -362,6 +364,9 @@ public: | |||
| #if ENABLE_GCODE_VIEWER | ||||
|     void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false) const; | ||||
| #endif // ENABLE_GCODE_VIEWER
 | ||||
|      | ||||
| 	const NotificationManager* get_notification_manager() const; | ||||
| 	NotificationManager* get_notification_manager(); | ||||
| 
 | ||||
|     // ROII wrapper for suppressing the Undo / Redo snapshot to be taken.
 | ||||
| 	class SuppressSnapshots | ||||
|  |  | |||
|  | @ -496,6 +496,7 @@ const std::vector<std::string>& Preset::sla_print_options() | |||
|             "support_head_penetration", | ||||
|             "support_head_width", | ||||
|             "support_pillar_diameter", | ||||
|             "support_small_pillar_diameter_percent", | ||||
|             "support_max_bridges_on_pillar", | ||||
|             "support_pillar_connection_mode", | ||||
|             "support_buildplate_only", | ||||
|  |  | |||
|  | @ -3919,6 +3919,7 @@ void TabSLAPrint::build() | |||
| 
 | ||||
|     optgroup = page->new_optgroup(L("Support pillar")); | ||||
|     optgroup->append_single_option_line("support_pillar_diameter"); | ||||
|     optgroup->append_single_option_line("support_small_pillar_diameter_percent"); | ||||
|     optgroup->append_single_option_line("support_max_bridges_on_pillar"); | ||||
|      | ||||
|     optgroup->append_single_option_line("support_pillar_connection_mode"); | ||||
|  |  | |||
|  | @ -27,6 +27,7 @@ | |||
| #include "slic3r/GUI/GUI_App.hpp" | ||||
| #include "slic3r/GUI/Plater.hpp" | ||||
| #include "slic3r/GUI/format.hpp" | ||||
| #include "slic3r/GUI/NotificationManager.hpp" | ||||
| #include "slic3r/Utils/Http.hpp" | ||||
| #include "slic3r/Config/Version.hpp" | ||||
| #include "slic3r/Config/Snapshot.hpp" | ||||
|  | @ -154,6 +155,9 @@ struct PresetUpdater::priv | |||
| 	bool cancel; | ||||
| 	std::thread thread; | ||||
| 
 | ||||
| 	bool has_waiting_updates { false }; | ||||
| 	Updates waiting_updates; | ||||
| 
 | ||||
| 	priv(); | ||||
| 
 | ||||
| 	void set_download_prefs(AppConfig *app_config); | ||||
|  | @ -165,6 +169,7 @@ struct PresetUpdater::priv | |||
| 	void check_install_indices() const; | ||||
| 	Updates get_config_updates(const Semver& old_slic3r_version) const; | ||||
| 	void perform_updates(Updates &&updates, bool snapshot = true) const; | ||||
| 	void set_waiting_updates(Updates u); | ||||
| }; | ||||
| 
 | ||||
| PresetUpdater::priv::priv() | ||||
|  | @ -326,7 +331,15 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors) | |||
| 				continue; | ||||
| 			} | ||||
| 			Slic3r::rename_file(idx_path_temp, idx_path); | ||||
| 			index = std::move(new_index); | ||||
| 			//if we rename path we need to change it in Index object too or create the object again
 | ||||
| 			//index = std::move(new_index);
 | ||||
| 			try { | ||||
| 				index.load(idx_path); | ||||
| 			} | ||||
| 			catch (const std::exception& /* err */) { | ||||
| 				BOOST_LOG_TRIVIAL(error) << format("Could not load downloaded index %1% for vendor %2%: invalid index?", idx_path, vendor.name); | ||||
| 				continue; | ||||
| 			} | ||||
| 			if (cancel) | ||||
| 				return; | ||||
| 		} | ||||
|  | @ -632,6 +645,12 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| void PresetUpdater::priv::set_waiting_updates(Updates u) | ||||
| { | ||||
| 	waiting_updates = u; | ||||
| 	has_waiting_updates = true; | ||||
| } | ||||
| 
 | ||||
| PresetUpdater::PresetUpdater() : | ||||
| 	p(new priv()) | ||||
| {} | ||||
|  | @ -690,9 +709,9 @@ void PresetUpdater::slic3r_update_notify() | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver &old_slic3r_version) const | ||||
| PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, bool no_notification) const | ||||
| { | ||||
| 	if (! p->enabled_config_update) { return R_NOOP; } | ||||
|  	if (! p->enabled_config_update) { return R_NOOP; } | ||||
| 
 | ||||
| 	auto updates = p->get_config_updates(old_slic3r_version); | ||||
| 	if (updates.incompats.size() > 0) { | ||||
|  | @ -779,30 +798,38 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver &old_slic3 | |||
| 		} | ||||
| 
 | ||||
| 		// regular update
 | ||||
| 		BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", updates.updates.size()); | ||||
| 		if (no_notification) { | ||||
| 			BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", p->waiting_updates.updates.size()); | ||||
| 
 | ||||
| 		std::vector<GUI::MsgUpdateConfig::Update> updates_msg; | ||||
| 		for (const auto &update : updates.updates) { | ||||
| 			std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string(); | ||||
| 			updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url)); | ||||
| 		} | ||||
| 			std::vector<GUI::MsgUpdateConfig::Update> updates_msg; | ||||
| 			for (const auto& update : updates.updates) { | ||||
| 				std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string(); | ||||
| 				updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url)); | ||||
| 			} | ||||
| 
 | ||||
| 		GUI::MsgUpdateConfig dlg(updates_msg); | ||||
| 			GUI::MsgUpdateConfig dlg(updates_msg); | ||||
| 
 | ||||
| 		const auto res = dlg.ShowModal(); | ||||
| 		if (res == wxID_OK) { | ||||
| 			BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; | ||||
| 			p->perform_updates(std::move(updates)); | ||||
| 			const auto res = dlg.ShowModal(); | ||||
| 			if (res == wxID_OK) { | ||||
| 				BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; | ||||
| 				p->perform_updates(std::move(updates)); | ||||
| 
 | ||||
| 			// Reload global configuration
 | ||||
| 			auto *app_config = GUI::wxGetApp().app_config; | ||||
| 			GUI::wxGetApp().preset_bundle->load_presets(*app_config); | ||||
| 			GUI::wxGetApp().load_current_presets(); | ||||
| 			return R_UPDATE_INSTALLED; | ||||
| 				// Reload global configuration
 | ||||
| 				auto* app_config = GUI::wxGetApp().app_config; | ||||
| 				GUI::wxGetApp().preset_bundle->load_presets(*app_config); | ||||
| 				GUI::wxGetApp().load_current_presets(); | ||||
| 				return R_UPDATE_INSTALLED; | ||||
| 			} | ||||
| 			else { | ||||
| 				BOOST_LOG_TRIVIAL(info) << "User refused the update"; | ||||
| 				return R_UPDATE_REJECT; | ||||
| 			} | ||||
| 		} else { | ||||
| 			BOOST_LOG_TRIVIAL(info) << "User refused the update"; | ||||
| 			return R_UPDATE_REJECT; | ||||
| 			p->set_waiting_updates(updates); | ||||
| 			GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::PresetUpdateAviable, *(GUI::wxGetApp().plater()->get_current_canvas3D())); | ||||
| 		} | ||||
| 		 | ||||
| 		// MsgUpdateConfig will show after the notificaation is clicked
 | ||||
| 	} else { | ||||
| 		BOOST_LOG_TRIVIAL(info) << "No configuration updates available."; | ||||
| 	} | ||||
|  | @ -825,5 +852,37 @@ void PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool | |||
| 	p->perform_updates(std::move(updates), snapshot); | ||||
| } | ||||
| 
 | ||||
| void PresetUpdater::on_update_notification_confirm() | ||||
| { | ||||
| 	if (!p->has_waiting_updates) | ||||
| 		return; | ||||
| 	BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", p->waiting_updates.updates.size()); | ||||
| 
 | ||||
| 	std::vector<GUI::MsgUpdateConfig::Update> updates_msg; | ||||
| 	for (const auto& update : p->waiting_updates.updates) { | ||||
| 		std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string(); | ||||
| 		updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url)); | ||||
| 	} | ||||
| 
 | ||||
| 	GUI::MsgUpdateConfig dlg(updates_msg); | ||||
| 
 | ||||
| 	const auto res = dlg.ShowModal(); | ||||
| 	if (res == wxID_OK) { | ||||
| 		BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; | ||||
| 		p->perform_updates(std::move(p->waiting_updates)); | ||||
| 
 | ||||
| 		// Reload global configuration
 | ||||
| 		auto* app_config = GUI::wxGetApp().app_config; | ||||
| 		GUI::wxGetApp().preset_bundle->load_presets(*app_config); | ||||
| 		GUI::wxGetApp().load_current_presets(); | ||||
| 		p->has_waiting_updates = false; | ||||
| 		//return R_UPDATE_INSTALLED;
 | ||||
| 	} | ||||
| 	else { | ||||
| 		BOOST_LOG_TRIVIAL(info) << "User refused the update"; | ||||
| 		//return R_UPDATE_REJECT;
 | ||||
| 	} | ||||
| 	 | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -35,16 +35,20 @@ public: | |||
| 		R_INCOMPAT_CONFIGURED, | ||||
| 		R_UPDATE_INSTALLED, | ||||
| 		R_UPDATE_REJECT, | ||||
| 		R_UPDATE_NOTIFICATION | ||||
| 	}; | ||||
| 
 | ||||
| 	// If updating is enabled, check if updates are available in cache, if so, ask about installation.
 | ||||
| 	// A false return value implies Slic3r should exit due to incompatibility of configuration.
 | ||||
| 	// Providing old slic3r version upgrade profiles on upgrade of an application even in case
 | ||||
| 	// that the config index installed from the Internet is equal to the index contained in the installation package.
 | ||||
| 	UpdateResult config_update(const Semver &old_slic3r_version) const; | ||||
| 	// no_notification = force modal textbox, otherwise some cases only shows notification
 | ||||
| 	UpdateResult config_update(const Semver &old_slic3r_version, bool no_notification) const; | ||||
| 
 | ||||
| 	// "Update" a list of bundles from resources (behaves like an online update).
 | ||||
| 	void install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const; | ||||
| 
 | ||||
| 	void on_update_notification_confirm(); | ||||
| private: | ||||
| 	struct priv; | ||||
| 	std::unique_ptr<priv> p; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 enricoturri1966
						enricoturri1966