Fixed conflicts after merge with master

This commit is contained in:
enricoturri1966 2020-08-04 09:58:19 +02:00
commit 25c3552555
68 changed files with 4042 additions and 1798 deletions

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="resin">
<rect x="4" y="7" fill="#ED6B21" width="8" height="8"/>
<path fill="none" stroke="#808080" stroke-linecap="round" stroke-miterlimit="10" d="M4.5,15h6.99c0.28,0,0.5-0.23,0.5-0.5V6
c0-1-2-1-2-2s0-1,0-1h1V1.5C11,1.23,10.77,1,10.5,1H5.5C5.23,1,5,1.23,5,1.5V3h1v1c0,1-2,1-2,2v8.5C4,14.77,4.23,15,4.5,15z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 671 B

View File

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="cross_megafocus.svg"
xml:space="preserve"
enable-background="new 0 0 16 16"
viewBox="0 0 16 16"
y="0px"
x="0px"
id="Layer_1"
version="1.0"><metadata
id="metadata16"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs14" /><sodipodi:namedview
inkscape:current-layer="Layer_1"
inkscape:window-maximized="1"
inkscape:window-y="-9"
inkscape:window-x="-9"
inkscape:cy="8"
inkscape:cx="8"
inkscape:zoom="47.0625"
showgrid="false"
id="namedview12"
inkscape:window-height="1721"
inkscape:window-width="3200"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<g
style="opacity:1;fill-opacity:1"
transform="matrix(1.1,0,0,1.1,-0.8,-0.8)"
id="cross">
<g
style="fill-opacity:1"
id="g4">
<line
style="fill-opacity:1"
id="line2"
y2="14"
x2="2"
y1="2"
x1="14"
stroke-miterlimit="10"
stroke-linecap="round"
stroke-width="3"
stroke="#ed6b21"
fill="none" />
</g>
<g
style="fill-opacity:1"
id="g8">
<line
style="fill-opacity:1"
id="line6"
y2="14"
x2="14"
y1="2"
x1="2"
stroke-miterlimit="10"
stroke-linecap="round"
stroke-width="3"
stroke="#ed6b21"
fill="none" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="timer_dot.svg"
xml:space="preserve"
enable-background="new 0 0 16 16"
viewBox="0 0 16 16"
y="0px"
x="0px"
id="Layer_1"
version="1.0"><metadata
id="metadata11"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs9"><linearGradient
id="linearGradient830"
inkscape:collect="always"><stop
id="stop826"
offset="0"
style="stop-color:#000000;stop-opacity:1;" /><stop
id="stop828"
offset="1"
style="stop-color:#000000;stop-opacity:0;" /></linearGradient><radialGradient
gradientUnits="userSpaceOnUse"
r="3.5"
fy="8"
fx="8"
cy="8"
cx="8"
id="radialGradient832"
xlink:href="#linearGradient830"
inkscape:collect="always" /></defs><sodipodi:namedview
inkscape:document-rotation="0"
inkscape:current-layer="Layer_1"
inkscape:window-maximized="1"
inkscape:window-y="-11"
inkscape:window-x="-11"
inkscape:cy="6.66147"
inkscape:cx="7.0304602"
inkscape:zoom="83.4386"
showgrid="false"
id="namedview7"
inkscape:window-height="2066"
inkscape:window-width="3840"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<g
transform="matrix(0.7,0,0,0.7,2.4,2.4)"
style="fill:#bf6637;fill-opacity:1;stroke:none;stroke-opacity:1"
id="g4">
<circle
style="fill:#bf6637;fill-opacity:1;stroke:none;stroke-opacity:1"
id="circle2"
r="3"
cy="8"
cx="8" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.0"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 16 16"
enable-background="new 0 0 16 16"
xml:space="preserve"
sodipodi:docname="timer_dot_empty.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata
id="metadata11"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs9"><linearGradient
inkscape:collect="always"
id="linearGradient830"><stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop826" /><stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop828" /></linearGradient><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient830"
id="radialGradient832"
cx="8"
cy="8"
fx="8"
fy="8"
r="3.5"
gradientUnits="userSpaceOnUse" /></defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="3840"
inkscape:window-height="2066"
id="namedview7"
showgrid="false"
inkscape:zoom="83.4386"
inkscape:cx="7.0304602"
inkscape:cy="6.66147"
inkscape:window-x="-11"
inkscape:window-y="-11"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" />
<g
id="g4"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1"
transform="matrix(0.7,0,0,0.7,2.4,2.4)">
<circle
cx="8"
cy="8"
r="3"
id="circle2"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -113,7 +113,12 @@ namespace ImGui
const char PrinterSlaIconMarker = 0x6; const char PrinterSlaIconMarker = 0x6;
const char FilamentIconMarker = 0x7; const char FilamentIconMarker = 0x7;
const char MaterialIconMarker = 0x8; 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); // void MyFunction(const char* name, const MyMatrix44& v);
} }

View File

@ -205,12 +205,13 @@ add_library(libslic3r STATIC
SimplifyMeshImpl.hpp SimplifyMeshImpl.hpp
SimplifyMesh.cpp SimplifyMesh.cpp
MarchingSquares.hpp MarchingSquares.hpp
Optimizer.hpp
${OpenVDBUtils_SOURCES} ${OpenVDBUtils_SOURCES}
SLA/Common.hpp
SLA/Common.cpp
SLA/Pad.hpp SLA/Pad.hpp
SLA/Pad.cpp SLA/Pad.cpp
SLA/SupportTreeBuilder.hpp SLA/SupportTreeBuilder.hpp
SLA/SupportTreeMesher.hpp
SLA/SupportTreeMesher.cpp
SLA/SupportTreeBuildsteps.hpp SLA/SupportTreeBuildsteps.hpp
SLA/SupportTreeBuildsteps.cpp SLA/SupportTreeBuildsteps.cpp
SLA/SupportTreeBuilder.cpp SLA/SupportTreeBuilder.cpp
@ -222,6 +223,7 @@ add_library(libslic3r STATIC
SLA/Rotfinder.cpp SLA/Rotfinder.cpp
SLA/BoostAdapter.hpp SLA/BoostAdapter.hpp
SLA/SpatIndex.hpp SLA/SpatIndex.hpp
SLA/SpatIndex.cpp
SLA/RasterBase.hpp SLA/RasterBase.hpp
SLA/RasterBase.cpp SLA/RasterBase.cpp
SLA/AGGRaster.hpp SLA/AGGRaster.hpp
@ -237,8 +239,10 @@ add_library(libslic3r STATIC
SLA/SupportPointGenerator.cpp SLA/SupportPointGenerator.cpp
SLA/Contour3D.hpp SLA/Contour3D.hpp
SLA/Contour3D.cpp SLA/Contour3D.cpp
SLA/EigenMesh3D.hpp SLA/IndexedMesh.hpp
SLA/IndexedMesh.cpp
SLA/Clustering.hpp SLA/Clustering.hpp
SLA/Clustering.cpp
SLA/ReprojectPointsOnMesh.hpp SLA/ReprojectPointsOnMesh.hpp
) )

View File

@ -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::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; std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print;
// Merge numerically very close Z values. // Merge numerically very close Z values.
for (size_t i = 0; i < ordering.size();) { for (size_t i = 0; i < ordering.size();) {
// Find the last layer with roughly the same print_z. // Find the last layer with roughly the same print_z.

View File

@ -2,7 +2,6 @@
#define OPENVDBUTILS_HPP #define OPENVDBUTILS_HPP
#include <libslic3r/TriangleMesh.hpp> #include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/SLA/Common.hpp>
#include <libslic3r/SLA/Contour3D.hpp> #include <libslic3r/SLA/Contour3D.hpp>
#include <openvdb/openvdb.h> #include <openvdb/openvdb.h>

380
src/libslic3r/Optimizer.hpp Normal file
View 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

View File

@ -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 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 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)); } template<class T, int N> Eigen::Matrix<T, 2, 1, Eigen::DontAlign>
inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); } to_2d(const Eigen::Matrix<T, N, 1, Eigen::DontAlign> &ptN) { return {ptN(0), ptN(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 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 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); } inline Vec3f to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); }

View File

@ -2715,7 +2715,7 @@ void PrintConfigDef::init_sla_params()
def->set_default_value(new ConfigOptionBool(true)); def->set_default_value(new ConfigOptionBool(true));
def = this->add("support_head_front_diameter", coFloat); 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->category = L("Supports");
def->tooltip = L("Diameter of the pointing side of the head"); def->tooltip = L("Diameter of the pointing side of the head");
def->sidetext = L("mm"); def->sidetext = L("mm");
@ -2724,7 +2724,7 @@ void PrintConfigDef::init_sla_params()
def->set_default_value(new ConfigOptionFloat(0.4)); def->set_default_value(new ConfigOptionFloat(0.4));
def = this->add("support_head_penetration", coFloat); def = this->add("support_head_penetration", coFloat);
def->label = L("Support head penetration"); def->label = L("Head penetration");
def->category = L("Supports"); def->category = L("Supports");
def->tooltip = L("How much the pinhead has to penetrate the model surface"); def->tooltip = L("How much the pinhead has to penetrate the model surface");
def->sidetext = L("mm"); def->sidetext = L("mm");
@ -2733,7 +2733,7 @@ void PrintConfigDef::init_sla_params()
def->set_default_value(new ConfigOptionFloat(0.2)); def->set_default_value(new ConfigOptionFloat(0.2));
def = this->add("support_head_width", coFloat); def = this->add("support_head_width", coFloat);
def->label = L("Support head width"); def->label = L("Pinhead width");
def->category = L("Supports"); def->category = L("Supports");
def->tooltip = L("Width from the back sphere center to the front sphere center"); def->tooltip = L("Width from the back sphere center to the front sphere center");
def->sidetext = L("mm"); def->sidetext = L("mm");
@ -2743,7 +2743,7 @@ void PrintConfigDef::init_sla_params()
def->set_default_value(new ConfigOptionFloat(1.0)); def->set_default_value(new ConfigOptionFloat(1.0));
def = this->add("support_pillar_diameter", coFloat); def = this->add("support_pillar_diameter", coFloat);
def->label = L("Support pillar diameter"); def->label = L("Pillar diameter");
def->category = L("Supports"); def->category = L("Supports");
def->tooltip = L("Diameter in mm of the support pillars"); def->tooltip = L("Diameter in mm of the support pillars");
def->sidetext = L("mm"); def->sidetext = L("mm");
@ -2751,6 +2751,17 @@ void PrintConfigDef::init_sla_params()
def->max = 15; def->max = 15;
def->mode = comSimple; def->mode = comSimple;
def->set_default_value(new ConfigOptionFloat(1.0)); 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 = this->add("support_max_bridges_on_pillar", coInt);
def->label = L("Max bridges on a pillar"); 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->set_default_value(new ConfigOptionInt(3));
def = this->add("support_pillar_connection_mode", coEnum); 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." def->tooltip = L("Controls the bridge type between two neighboring pillars."
" Can be zig-zag, cross (double zig-zag) or dynamic which" " Can be zig-zag, cross (double zig-zag) or dynamic which"
" will automatically switch between the first two depending" " will automatically switch between the first two depending"

View File

@ -1018,6 +1018,10 @@ public:
// Radius in mm of the support pillars. // Radius in mm of the support pillars.
ConfigOptionFloat support_pillar_diameter /*= 0.8*/; 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. // How much bridge (supporting another pinhead) can be placed on a pillar.
ConfigOptionInt support_max_bridges_on_pillar; ConfigOptionInt support_max_bridges_on_pillar;
@ -1142,6 +1146,7 @@ protected:
OPT_PTR(support_head_penetration); OPT_PTR(support_head_penetration);
OPT_PTR(support_head_width); OPT_PTR(support_head_width);
OPT_PTR(support_pillar_diameter); OPT_PTR(support_pillar_diameter);
OPT_PTR(support_small_pillar_diameter_percent);
OPT_PTR(support_max_bridges_on_pillar); OPT_PTR(support_max_bridges_on_pillar);
OPT_PTR(support_pillar_connection_mode); OPT_PTR(support_pillar_connection_mode);
OPT_PTR(support_buildplate_only); OPT_PTR(support_buildplate_only);

View File

@ -1,7 +1,9 @@
#ifndef SLA_BOOSTADAPTER_HPP #ifndef SLA_BOOSTADAPTER_HPP
#define SLA_BOOSTADAPTER_HPP #define SLA_BOOSTADAPTER_HPP
#include <libslic3r/SLA/Common.hpp> #include <libslic3r/Point.hpp>
#include <libslic3r/BoundingBox.hpp>
#include <boost/geometry.hpp> #include <boost/geometry.hpp>
namespace boost { namespace boost {

View 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

View File

@ -2,7 +2,8 @@
#define SLA_CLUSTERING_HPP #define SLA_CLUSTERING_HPP
#include <vector> #include <vector>
#include <libslic3r/SLA/Common.hpp>
#include <libslic3r/Point.hpp>
#include <libslic3r/SLA/SpatIndex.hpp> #include <libslic3r/SLA/SpatIndex.hpp>
namespace Slic3r { namespace sla { namespace Slic3r { namespace sla {
@ -16,7 +17,7 @@ ClusteredPoints cluster(const std::vector<unsigned>& indices,
double dist, double dist,
unsigned max_points); unsigned max_points);
ClusteredPoints cluster(const PointSet& points, ClusteredPoints cluster(const Eigen::MatrixXd& points,
double dist, double dist,
unsigned max_points); unsigned max_points);
@ -26,5 +27,56 @@ ClusteredPoints cluster(
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate, std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
unsigned max_points); 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 #endif // CLUSTERING_HPP

View File

@ -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

View File

@ -1,5 +1,5 @@
#include <libslic3r/SLA/Contour3D.hpp> #include <libslic3r/SLA/Contour3D.hpp>
#include <libslic3r/SLA/EigenMesh3D.hpp> #include <libslic3r/SLA/IndexedMesh.hpp>
#include <libslic3r/Format/objparser.hpp> #include <libslic3r/Format/objparser.hpp>
@ -27,7 +27,7 @@ Contour3D::Contour3D(TriangleMesh &&trmesh)
faces3.swap(trmesh.its.indices); faces3.swap(trmesh.its.indices);
} }
Contour3D::Contour3D(const EigenMesh3D &emesh) { Contour3D::Contour3D(const IndexedMesh &emesh) {
points.reserve(emesh.vertices().size()); points.reserve(emesh.vertices().size());
faces3.reserve(emesh.indices().size()); faces3.reserve(emesh.indices().size());

View File

@ -1,13 +1,16 @@
#ifndef SLA_CONTOUR3D_HPP #ifndef SLA_CONTOUR3D_HPP
#define SLA_CONTOUR3D_HPP #define SLA_CONTOUR3D_HPP
#include <libslic3r/SLA/Common.hpp>
#include <libslic3r/TriangleMesh.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 /// 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. /// other meshes of this type and converting to and from other mesh formats.
@ -19,7 +22,7 @@ struct Contour3D {
Contour3D() = default; Contour3D() = default;
Contour3D(const TriangleMesh &trmesh); Contour3D(const TriangleMesh &trmesh);
Contour3D(TriangleMesh &&trmesh); Contour3D(TriangleMesh &&trmesh);
Contour3D(const EigenMesh3D &emesh); Contour3D(const IndexedMesh &emesh);
Contour3D& merge(const Contour3D& ctr); Contour3D& merge(const Contour3D& ctr);
Contour3D& merge(const Pointf3s& triangles); Contour3D& merge(const Pointf3s& triangles);

View File

@ -3,11 +3,10 @@
#include <libslic3r/OpenVDBUtils.hpp> #include <libslic3r/OpenVDBUtils.hpp>
#include <libslic3r/TriangleMesh.hpp> #include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/SLA/Hollowing.hpp> #include <libslic3r/SLA/Hollowing.hpp>
#include <libslic3r/SLA/Contour3D.hpp> #include <libslic3r/SLA/IndexedMesh.hpp>
#include <libslic3r/SLA/EigenMesh3D.hpp>
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
#include <libslic3r/ClipperUtils.hpp> #include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/SimplifyMesh.hpp> #include <libslic3r/SimplifyMesh.hpp>
#include <libslic3r/SLA/SupportTreeMesher.hpp>
#include <boost/log/trivial.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()); const Eigen::ParametrizedLine<float, 3> ray(s, dir.normalized());
for (size_t i=0; i<2; ++i) 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); const float sqr_radius = pow(radius, 2.f);

View File

@ -2,7 +2,6 @@
#define SLA_HOLLOWING_HPP #define SLA_HOLLOWING_HPP
#include <memory> #include <memory>
#include <libslic3r/SLA/Common.hpp>
#include <libslic3r/SLA/Contour3D.hpp> #include <libslic3r/SLA/Contour3D.hpp>
#include <libslic3r/SLA/JobController.hpp> #include <libslic3r/SLA/JobController.hpp>

View File

@ -1,187 +1,18 @@
#include <cmath> #include "IndexedMesh.hpp"
#include <libslic3r/SLA/Common.hpp> #include "Concurrency.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 <libslic3r/AABBTreeIndirect.hpp> #include <libslic3r/AABBTreeIndirect.hpp>
#include <libslic3r/TriangleMesh.hpp>
// for concave hull merging decisions #include <numeric>
#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>
#ifdef SLIC3R_HOLE_RAYCASTER #ifdef SLIC3R_HOLE_RAYCASTER
#include <libslic3r/SLA/Hollowing.hpp> #include <libslic3r/SLA/Hollowing.hpp>
#endif #endif
namespace Slic3r { namespace sla {
#ifdef _MSC_VER class IndexedMesh::AABBImpl {
#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 {
private: private:
AABBTreeIndirect::Tree3f m_tree; AABBTreeIndirect::Tree3f m_tree;
@ -189,7 +20,7 @@ public:
void init(const TriangleMesh& tm) void init(const TriangleMesh& tm)
{ {
m_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( 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, void intersect_ray(const TriangleMesh& tm,
@ -215,9 +46,9 @@ public:
size_t idx_unsigned = 0; size_t idx_unsigned = 0;
Vec3d closest_vec3d(closest); Vec3d closest_vec3d(closest);
double dist = AABBTreeIndirect::squared_distance_to_indexed_triangle_set( double dist = AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
tm.its.vertices, tm.its.vertices,
tm.its.indices, tm.its.indices,
m_tree, point, idx_unsigned, closest_vec3d); m_tree, point, idx_unsigned, closest_vec3d);
i = int(idx_unsigned); i = int(idx_unsigned);
closest = closest_vec3d; closest = closest_vec3d;
return dist; return dist;
@ -226,72 +57,71 @@ public:
static const constexpr double MESH_EPS = 1e-6; 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) : m_aabb(new AABBImpl()), m_tm(&tmesh)
{ {
auto&& bb = tmesh.bounding_box(); auto&& bb = tmesh.bounding_box();
m_ground_level += bb.min(Z); m_ground_level += bb.min(Z);
// Build the AABB accelaration tree // Build the AABB accelaration tree
m_aabb->init(tmesh); 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_tm(other.m_tm), m_ground_level(other.m_ground_level),
m_aabb( new AABBImpl(*other.m_aabb) ) {} 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_tm = other.m_tm;
m_ground_level = other.m_ground_level; m_ground_level = other.m_ground_level;
m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this; 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; return m_tm->its.vertices;
} }
const std::vector<Vec3i>& EigenMesh3D::indices() const const std::vector<Vec3i>& IndexedMesh::indices() const
{ {
return m_tm->its.indices; 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]; 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]; 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>(); return m_tm->stl.facet_start[face_id].normal.cast<double>();
} }
IndexedMesh::hit_result
EigenMesh3D::hit_result IndexedMesh::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
{ {
assert(is_approx(dir.norm(), 1.)); assert(is_approx(dir.norm(), 1.));
igl::Hit hit; igl::Hit hit;
@ -319,13 +149,13 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
return ret; return ret;
} }
std::vector<EigenMesh3D::hit_result> std::vector<IndexedMesh::hit_result>
EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const 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; std::vector<igl::Hit> hits;
m_aabb->intersect_ray(*m_tm, s, dir, hits); m_aabb->intersect_ray(*m_tm, s, dir, hits);
// The sort is necessary, the hits are not always sorted. // The sort is necessary, the hits are not always sorted.
std::sort(hits.begin(), hits.end(), std::sort(hits.begin(), hits.end(),
[](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; }); [](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 (?) // along an axis of a cube due to floating-point approximations in igl (?)
hits.erase(std::unique(hits.begin(), hits.end(), hits.erase(std::unique(hits.begin(), hits.end(),
[](const igl::Hit& a, const igl::Hit& b) [](const igl::Hit& a, const igl::Hit& b)
{ return a.t == b.t; }), { return a.t == b.t; }),
hits.end()); hits.end());
// Convert the igl::Hit into hit_result // Convert the igl::Hit into hit_result
outs.reserve(hits.size()); outs.reserve(hits.size());
for (const igl::Hit& hit : hits) { 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_t = double(hit.t);
outs.back().m_dir = dir; outs.back().m_dir = dir;
outs.back().m_source = s; outs.back().m_source = s;
@ -355,8 +185,8 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
#ifdef SLIC3R_HOLE_RAYCASTER #ifdef SLIC3R_HOLE_RAYCASTER
EigenMesh3D::hit_result EigenMesh3D::filter_hits( IndexedMesh::hit_result IndexedMesh::filter_hits(
const std::vector<EigenMesh3D::hit_result>& object_hits) const const std::vector<IndexedMesh::hit_result>& object_hits) const
{ {
assert(! m_holes.empty()); assert(! m_holes.empty());
hit_result out(*this); hit_result out(*this);
@ -377,7 +207,7 @@ EigenMesh3D::hit_result EigenMesh3D::filter_hits(
}; };
std::vector<HoleHit> hole_isects; std::vector<HoleHit> hole_isects;
hole_isects.reserve(m_holes.size()); hole_isects.reserve(m_holes.size());
auto sf = s.cast<float>(); auto sf = s.cast<float>();
auto dirf = dir.cast<float>(); auto dirf = dir.cast<float>();
@ -452,7 +282,7 @@ EigenMesh3D::hit_result EigenMesh3D::filter_hits(
#endif #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; double sqdst = 0;
Eigen::Matrix<double, 1, 3> pp = p; Eigen::Matrix<double, 1, 3> pp = p;
Eigen::Matrix<double, 1, 3> cc; Eigen::Matrix<double, 1, 3> cc;
@ -461,31 +291,19 @@ double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
return sqdst; return sqdst;
} }
/* ****************************************************************************
* Misc functions
* ****************************************************************************/
namespace { static bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
double eps = 0.05)
bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
double eps = 0.05)
{ {
using Line3D = Eigen::ParametrizedLine<double, 3>; using Line3D = Eigen::ParametrizedLine<double, 3>;
auto line = Line3D::Through(e1, e2); auto line = Line3D::Through(e1, e2);
double d = line.distance(p); double d = line.distance(p);
return std::abs(d) < eps; 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, PointSet normals(const PointSet& points,
const EigenMesh3D& mesh, const IndexedMesh& mesh,
double eps, double eps,
std::function<void()> thr, // throw on cancel std::function<void()> thr, // throw on cancel
const std::vector<unsigned>& pt_indices) const std::vector<unsigned>& pt_indices)
@ -531,11 +349,11 @@ PointSet normals(const PointSet& points,
// ic will mark a single vertex. // ic will mark a single vertex.
int ia = -1, ib = -1, ic = -1; int ia = -1, ib = -1, ic = -1;
if (std::abs(distance(p, p1)) < eps) { if (std::abs((p - p1).norm()) < eps) {
ic = trindex(0); ic = trindex(0);
} else if (std::abs(distance(p, p2)) < eps) { } else if (std::abs((p - p2).norm()) < eps) {
ic = trindex(1); ic = trindex(1);
} else if (std::abs(distance(p, p3)) < eps) { } else if (std::abs((p - p3).norm()) < eps) {
ic = trindex(2); ic = trindex(2);
} else if (point_on_edge(p, p1, p2, eps)) { } else if (point_on_edge(p, p1, p2, eps)) {
ia = trindex(0); ia = trindex(0);
@ -612,148 +430,4 @@ PointSet normals(const PointSet& points,
return ret; return ret;
} }
namespace bgi = boost::geometry::index; }} // namespace Slic3r::sla
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

View File

@ -1,8 +1,10 @@
#ifndef SLA_EIGENMESH3D_H #ifndef SLA_INDEXEDMESH_H
#define SLA_EIGENMESH3D_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 // There is an implementation of a hole-aware raycaster that was eventually
// not used in production version. It is now hidden under following define // not used in production version. It is now hidden under following define
@ -19,10 +21,12 @@ class TriangleMesh;
namespace sla { namespace sla {
using PointSet = Eigen::MatrixXd;
/// An index-triangle structure for libIGL functions. Also serves as an /// An index-triangle structure for libIGL functions. Also serves as an
/// alternative (raw) input format for the SLASupportTree. /// alternative (raw) input format for the SLASupportTree.
// Implemented in libslic3r/SLA/Common.cpp // Implemented in libslic3r/SLA/Common.cpp
class EigenMesh3D { class IndexedMesh {
class AABBImpl; class AABBImpl;
const TriangleMesh* m_tm; const TriangleMesh* m_tm;
@ -38,15 +42,15 @@ class EigenMesh3D {
public: public:
explicit EigenMesh3D(const TriangleMesh&); explicit IndexedMesh(const TriangleMesh&);
EigenMesh3D(const EigenMesh3D& other); IndexedMesh(const IndexedMesh& other);
EigenMesh3D& operator=(const EigenMesh3D&); IndexedMesh& operator=(const IndexedMesh&);
EigenMesh3D(EigenMesh3D &&other); IndexedMesh(IndexedMesh &&other);
EigenMesh3D& operator=(EigenMesh3D &&other); IndexedMesh& operator=(IndexedMesh &&other);
~EigenMesh3D(); ~IndexedMesh();
inline double ground_level() const { return m_ground_level + m_gnd_offset; } inline double ground_level() const { return m_ground_level + m_gnd_offset; }
inline void ground_level_offset(double o) { m_gnd_offset = o; } 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. // m_t holds a distance from m_source to the intersection.
double m_t = infty(); double m_t = infty();
int m_face_id = -1; int m_face_id = -1;
const EigenMesh3D *m_mesh = nullptr; const IndexedMesh *m_mesh = nullptr;
Vec3d m_dir; Vec3d m_dir;
Vec3d m_source; Vec3d m_source;
Vec3d m_normal; Vec3d m_normal;
friend class EigenMesh3D; friend class IndexedMesh;
// A valid object of this class can only be obtained from // A valid object of this class can only be obtained from
// EigenMesh3D::query_ray_hit method. // IndexedMesh::query_ray_hit method.
explicit inline hit_result(const EigenMesh3D& em): m_mesh(&em) {} explicit inline hit_result(const IndexedMesh& em): m_mesh(&em) {}
public: public:
// This denotes no hit on the mesh. // This denotes no hit on the mesh.
static inline constexpr double infty() { return std::numeric_limits<double>::infinity(); } 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 Vec3d position() const { return m_source + m_dir * m_t; }
inline int face() const { return m_face_id; } inline int face() const { return m_face_id; }
inline bool is_valid() const { return m_mesh != nullptr; } 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 { inline const Vec3d& normal() const {
assert(is_valid()); assert(is_valid());
@ -107,7 +111,7 @@ public:
// This function is currently not used anywhere, it was written when the // This function is currently not used anywhere, it was written when the
// holes were subtracted on slices, that is, before we started using CGAL // holes were subtracted on slices, that is, before we started using CGAL
// to actually cut the holes into the mesh. // 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 #endif
// Casting a ray on the mesh, returns the distance where the hit occures. // 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; 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 // Calculate the normals for the selected points (from 'points' set) on the
// mesh. This will call squared distance for each point. // mesh. This will call squared distance for each point.
PointSet normals(const PointSet& points, PointSet normals(const PointSet& points,
const EigenMesh3D& convert_mesh, const IndexedMesh& convert_mesh,
double eps = 0.05, // min distance from edges double eps = 0.05, // min distance from edges
std::function<void()> throw_on_cancel = [](){}, std::function<void()> throw_on_cancel = [](){},
const std::vector<unsigned>& selected_points = {}); const std::vector<unsigned>& selected_points = {});
}} // namespace Slic3r::sla }} // namespace Slic3r::sla
#endif // EIGENMESH3D_H #endif // INDEXEDMESH_H

View File

@ -2,6 +2,7 @@
#define SLA_JOBCONTROLLER_HPP #define SLA_JOBCONTROLLER_HPP
#include <functional> #include <functional>
#include <string>
namespace Slic3r { namespace sla { namespace Slic3r { namespace sla {

View File

@ -1,5 +1,4 @@
#include <libslic3r/SLA/Pad.hpp> #include <libslic3r/SLA/Pad.hpp>
#include <libslic3r/SLA/Common.hpp>
#include <libslic3r/SLA/SpatIndex.hpp> #include <libslic3r/SLA/SpatIndex.hpp>
#include <libslic3r/SLA/BoostAdapter.hpp> #include <libslic3r/SLA/BoostAdapter.hpp>
#include <libslic3r/SLA/Contour3D.hpp> #include <libslic3r/SLA/Contour3D.hpp>

View File

@ -4,7 +4,7 @@
#include "libslic3r/Point.hpp" #include "libslic3r/Point.hpp"
#include "SupportPoint.hpp" #include "SupportPoint.hpp"
#include "Hollowing.hpp" #include "Hollowing.hpp"
#include "EigenMesh3D.hpp" #include "IndexedMesh.hpp"
#include "libslic3r/Model.hpp" #include "libslic3r/Model.hpp"
#include <tbb/parallel_for.h> #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 Pt> void pos(Pt &p, const Vec3d &pp) { p.pos = pp.cast<float>(); }
template<class PointType> 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) { tbb::parallel_for(size_t(0), pts.size(), [&mesh, &pts](size_t idx) {
int junk; int junk;
@ -40,7 +40,7 @@ inline void reproject_points_and_holes(ModelObject *object)
TriangleMesh rmsh = object->raw_mesh(); TriangleMesh rmsh = object->raw_mesh();
rmsh.require_shared_vertices(); rmsh.require_shared_vertices();
EigenMesh3D emesh{rmsh}; IndexedMesh emesh{rmsh};
if (has_sppoints) if (has_sppoints)
reproject_support_points(emesh, object->sla_support_points); reproject_support_points(emesh, object->sla_support_points);

View File

@ -2,7 +2,6 @@
#include <exception> #include <exception>
#include <libnest2d/optimizers/nlopt/genetic.hpp> #include <libnest2d/optimizers/nlopt/genetic.hpp>
#include <libslic3r/SLA/Common.hpp>
#include <libslic3r/SLA/Rotfinder.hpp> #include <libslic3r/SLA/Rotfinder.hpp>
#include <libslic3r/SLA/SupportTree.hpp> #include <libslic3r/SLA/SupportTree.hpp>
#include "Model.hpp" #include "Model.hpp"

View 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

View File

@ -73,7 +73,7 @@ public:
BoxIndex& operator=(BoxIndex&&); BoxIndex& operator=(BoxIndex&&);
void insert(const BoxIndexEl&); 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))); insert(std::make_pair(bb, unsigned(idx)));
} }

View File

@ -2,7 +2,6 @@
#define SLA_SUPPORTPOINT_HPP #define SLA_SUPPORTPOINT_HPP
#include <vector> #include <vector>
#include <libslic3r/SLA/Common.hpp>
#include <libslic3r/ExPolygon.hpp> #include <libslic3r/ExPolygon.hpp>
namespace Slic3r { namespace sla { namespace Slic3r { namespace sla {
@ -29,13 +28,13 @@ struct SupportPoint
float pos_y, float pos_y,
float pos_z, float pos_z,
float head_radius, float head_radius,
bool new_island) bool new_island = false)
: pos(pos_x, pos_y, pos_z) : pos(pos_x, pos_y, pos_z)
, head_front_radius(head_radius) , head_front_radius(head_radius)
, is_new_island(new_island) , 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) : pos(position)
, head_front_radius(head_radius) , head_front_radius(head_radius)
, is_new_island(new_island) , is_new_island(new_island)

View File

@ -50,7 +50,7 @@ float SupportPointGenerator::distance_limit(float angle) const
}*/ }*/
SupportPointGenerator::SupportPointGenerator( SupportPointGenerator::SupportPointGenerator(
const sla::EigenMesh3D &emesh, const sla::IndexedMesh &emesh,
const std::vector<ExPolygons> &slices, const std::vector<ExPolygons> &slices,
const std::vector<float> & heights, const std::vector<float> & heights,
const Config & config, const Config & config,
@ -64,7 +64,7 @@ SupportPointGenerator::SupportPointGenerator(
} }
SupportPointGenerator::SupportPointGenerator( SupportPointGenerator::SupportPointGenerator(
const EigenMesh3D &emesh, const IndexedMesh &emesh,
const SupportPointGenerator::Config &config, const SupportPointGenerator::Config &config,
std::function<void ()> throw_on_cancel, std::function<void ()> throw_on_cancel,
std::function<void (int)> statusfn) std::function<void (int)> statusfn)
@ -95,8 +95,8 @@ void SupportPointGenerator::project_onto_mesh(std::vector<sla::SupportPoint>& po
m_throw_on_cancel(); m_throw_on_cancel();
Vec3f& p = points[point_id].pos; Vec3f& p = points[point_id].pos;
// Project the point upward and downward and choose the closer intersection with the mesh. // 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::IndexedMesh::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_down = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., -1.));
bool up = hit_up.is_hit(); bool up = hit_up.is_hit();
bool down = hit_down.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) if (!up && !down)
continue; 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>(); 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 // get iterator to the reorganized vector end
auto endit = auto endit = std::remove_if(pts.begin(), pts.end(), [lvl]
std::remove_if(pts.begin(), pts.end(), (const sla::SupportPoint &sp) {
[tolerance, gnd_lvl](const sla::SupportPoint &sp) { return sp.pos.z() <= lvl;
double diff = std::abs(gnd_lvl -
double(sp.pos(Z)));
return diff <= tolerance;
}); });
// erase all elements after the new end // erase all elements after the new end

View File

@ -3,9 +3,8 @@
#include <random> #include <random>
#include <libslic3r/SLA/Common.hpp>
#include <libslic3r/SLA/SupportPoint.hpp> #include <libslic3r/SLA/SupportPoint.hpp>
#include <libslic3r/SLA/EigenMesh3D.hpp> #include <libslic3r/SLA/IndexedMesh.hpp>
#include <libslic3r/BoundingBox.hpp> #include <libslic3r/BoundingBox.hpp>
#include <libslic3r/ClipperUtils.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) 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); 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; } const std::vector<SupportPoint>& output() const { return m_output; }
std::vector<SupportPoint>& output() { return m_output; } std::vector<SupportPoint>& output() { return m_output; }
@ -207,14 +206,14 @@ private:
static void output_structures(const std::vector<Structure> &structures); static void output_structures(const std::vector<Structure> &structures);
#endif // SLA_SUPPORTPOINTGEN_DEBUG #endif // SLA_SUPPORTPOINTGEN_DEBUG
const EigenMesh3D& m_emesh; const IndexedMesh& m_emesh;
std::function<void(void)> m_throw_on_cancel; std::function<void(void)> m_throw_on_cancel;
std::function<void(int)> m_statusfn; std::function<void(int)> m_statusfn;
std::mt19937 m_rng; 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 }} // namespace Slic3r::sla

View File

@ -5,9 +5,9 @@
#include <numeric> #include <numeric>
#include <libslic3r/SLA/SupportTree.hpp> #include <libslic3r/SLA/SupportTree.hpp>
#include <libslic3r/SLA/Common.hpp>
#include <libslic3r/SLA/SpatIndex.hpp> #include <libslic3r/SLA/SpatIndex.hpp>
#include <libslic3r/SLA/SupportTreeBuilder.hpp> #include <libslic3r/SLA/SupportTreeBuilder.hpp>
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
#include <libslic3r/MTUtils.hpp> #include <libslic3r/MTUtils.hpp>
#include <libslic3r/ClipperUtils.hpp> #include <libslic3r/ClipperUtils.hpp>
@ -28,20 +28,6 @@
namespace Slic3r { namespace Slic3r {
namespace sla { 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 { void SupportTree::retrieve_full_mesh(TriangleMesh &outmesh) const {
outmesh.merge(retrieve_mesh(MeshType::Support)); outmesh.merge(retrieve_mesh(MeshType::Support));
outmesh.merge(retrieve_mesh(MeshType::Pad)); outmesh.merge(retrieve_mesh(MeshType::Pad));
@ -103,9 +89,11 @@ SupportTree::UPtr SupportTree::create(const SupportableMesh &sm,
builder->m_ctl = ctl; builder->m_ctl = ctl;
if (sm.cfg.enabled) { 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. builder->merge_and_cleanup(); // clean metadata, leave only the meshes.
} else { } else {
// If a pad gets added later, it will be in the right Z level
builder->ground_level = sm.emesh.ground_level(); builder->ground_level = sm.emesh.ground_level();
} }

View File

@ -5,9 +5,8 @@
#include <memory> #include <memory>
#include <Eigen/Geometry> #include <Eigen/Geometry>
#include <libslic3r/SLA/Common.hpp>
#include <libslic3r/SLA/Pad.hpp> #include <libslic3r/SLA/Pad.hpp>
#include <libslic3r/SLA/EigenMesh3D.hpp> #include <libslic3r/SLA/IndexedMesh.hpp>
#include <libslic3r/SLA/SupportPoint.hpp> #include <libslic3r/SLA/SupportPoint.hpp>
#include <libslic3r/SLA/JobController.hpp> #include <libslic3r/SLA/JobController.hpp>
@ -32,7 +31,7 @@ enum class PillarConnectionMode
dynamic dynamic
}; };
struct SupportConfig struct SupportTreeConfig
{ {
bool enabled = true; bool enabled = true;
@ -45,6 +44,8 @@ struct SupportConfig
// Radius of the back side of the 3d arrow. // Radius of the back side of the 3d arrow.
double head_back_radius_mm = 0.5; 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. // Width in mm from the back sphere center to the front sphere center.
double head_width_mm = 1.0; 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. // 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 // 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 constexpr max_solo_pillar_height_mm = 15.0;
static const double max_dual_pillar_height_mm; static const double constexpr max_dual_pillar_height_mm = 35.0;
static const double optimizer_rel_score_diff; static const double constexpr optimizer_rel_score_diff = 1e-6;
static const unsigned optimizer_max_iterations; static const unsigned constexpr optimizer_max_iterations = 1000;
static const unsigned pillar_cascade_neighbors; 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 }; enum class MeshType { Support, Pad };
struct SupportableMesh struct SupportableMesh
{ {
EigenMesh3D emesh; IndexedMesh emesh;
SupportPoints pts; SupportPoints pts;
SupportConfig cfg; SupportTreeConfig cfg;
PadConfig pad_cfg;
explicit SupportableMesh(const TriangleMesh & trmsh, explicit SupportableMesh(const TriangleMesh & trmsh,
const SupportPoints &sp, const SupportPoints &sp,
const SupportConfig &c) const SupportTreeConfig &c)
: emesh{trmsh}, pts{sp}, cfg{c} : emesh{trmsh}, pts{sp}, cfg{c}
{} {}
explicit SupportableMesh(const EigenMesh3D &em, explicit SupportableMesh(const IndexedMesh &em,
const SupportPoints &sp, const SupportPoints &sp,
const SupportConfig &c) const SupportTreeConfig &c)
: emesh{em}, pts{sp}, cfg{c} : emesh{em}, pts{sp}, cfg{c}
{} {}
}; };

View File

@ -1,336 +1,26 @@
#define NOMINMAX
#include <libslic3r/SLA/SupportTreeBuilder.hpp> #include <libslic3r/SLA/SupportTreeBuilder.hpp>
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp> #include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
#include <libslic3r/SLA/SupportTreeMesher.hpp>
#include <libslic3r/SLA/Contour3D.hpp> #include <libslic3r/SLA/Contour3D.hpp>
namespace Slic3r { namespace Slic3r {
namespace sla { 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, Head::Head(double r_big_mm,
double r_small_mm, double r_small_mm,
double length_mm, double length_mm,
double penetration, double penetration,
const Vec3d &direction, const Vec3d &direction,
const Vec3d &offset, const Vec3d &offset)
const size_t circlesteps) : dir(direction)
: steps(circlesteps) , pos(offset)
, dir(direction)
, tr(offset)
, r_back_mm(r_big_mm) , r_back_mm(r_big_mm)
, r_pin_mm(r_small_mm) , r_pin_mm(r_small_mm)
, width_mm(length_mm) , width_mm(length_mm)
, penetration_mm(penetration) , 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, Pad::Pad(const TriangleMesh &support_mesh,
@ -368,7 +58,6 @@ SupportTreeBuilder::SupportTreeBuilder(SupportTreeBuilder &&o)
, m_pillars{std::move(o.m_pillars)} , m_pillars{std::move(o.m_pillars)}
, m_bridges{std::move(o.m_bridges)} , m_bridges{std::move(o.m_bridges)}
, m_crossbridges{std::move(o.m_crossbridges)} , m_crossbridges{std::move(o.m_crossbridges)}
, m_compact_bridges{std::move(o.m_compact_bridges)}
, m_pad{std::move(o.m_pad)} , m_pad{std::move(o.m_pad)}
, m_meshcache{std::move(o.m_meshcache)} , m_meshcache{std::move(o.m_meshcache)}
, m_meshcache_valid{o.m_meshcache_valid} , m_meshcache_valid{o.m_meshcache_valid}
@ -382,7 +71,6 @@ SupportTreeBuilder::SupportTreeBuilder(const SupportTreeBuilder &o)
, m_pillars{o.m_pillars} , m_pillars{o.m_pillars}
, m_bridges{o.m_bridges} , m_bridges{o.m_bridges}
, m_crossbridges{o.m_crossbridges} , m_crossbridges{o.m_crossbridges}
, m_compact_bridges{o.m_compact_bridges}
, m_pad{o.m_pad} , m_pad{o.m_pad}
, m_meshcache{o.m_meshcache} , m_meshcache{o.m_meshcache}
, m_meshcache_valid{o.m_meshcache_valid} , m_meshcache_valid{o.m_meshcache_valid}
@ -397,7 +85,6 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(SupportTreeBuilder &&o)
m_pillars = std::move(o.m_pillars); m_pillars = std::move(o.m_pillars);
m_bridges = std::move(o.m_bridges); m_bridges = std::move(o.m_bridges);
m_crossbridges = std::move(o.m_crossbridges); m_crossbridges = std::move(o.m_crossbridges);
m_compact_bridges = std::move(o.m_compact_bridges);
m_pad = std::move(o.m_pad); m_pad = std::move(o.m_pad);
m_meshcache = std::move(o.m_meshcache); m_meshcache = std::move(o.m_meshcache);
m_meshcache_valid = o.m_meshcache_valid; m_meshcache_valid = o.m_meshcache_valid;
@ -413,7 +100,6 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o)
m_pillars = o.m_pillars; m_pillars = o.m_pillars;
m_bridges = o.m_bridges; m_bridges = o.m_bridges;
m_crossbridges = o.m_crossbridges; m_crossbridges = o.m_crossbridges;
m_compact_bridges = o.m_compact_bridges;
m_pad = o.m_pad; m_pad = o.m_pad;
m_meshcache = o.m_meshcache; m_meshcache = o.m_meshcache;
m_meshcache_valid = o.m_meshcache_valid; m_meshcache_valid = o.m_meshcache_valid;
@ -422,7 +108,19 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o)
return *this; 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; if (m_meshcache_valid) return m_meshcache;
@ -430,35 +128,44 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh() const
for (auto &head : m_heads) { for (auto &head : m_heads) {
if (ctl().stopcondition()) break; 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; if (ctl().stopcondition()) break;
merged.merge(stick.mesh); merged.merge(get_mesh(pill, steps));
merged.merge(stick.base); }
for (auto &pedest : m_pedestals) {
if (ctl().stopcondition()) break;
merged.merge(get_mesh(pedest, steps));
} }
for (auto &j : m_junctions) { for (auto &j : m_junctions) {
if (ctl().stopcondition()) break; 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) { for (auto &bs : m_bridges) {
if (ctl().stopcondition()) break; if (ctl().stopcondition()) break;
merged.merge(bs.mesh); merged.merge(get_mesh(bs, steps));
} }
for (auto &bs : m_crossbridges) { for (auto &bs : m_crossbridges) {
if (ctl().stopcondition()) break; 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()) { if (ctl().stopcondition()) {
// In case of failure we have to return an empty mesh // In case of failure we have to return an empty mesh
m_meshcache = TriangleMesh(); m_meshcache = TriangleMesh();
@ -499,7 +206,6 @@ const TriangleMesh &SupportTreeBuilder::merge_and_cleanup()
m_pillars = {}; m_pillars = {};
m_junctions = {}; m_junctions = {};
m_bridges = {}; m_bridges = {};
m_compact_bridges = {};
return ret; return ret;
} }
@ -514,11 +220,4 @@ const TriangleMesh &SupportTreeBuilder::retrieve_mesh(MeshType meshtype) const
return m_meshcache; return m_meshcache;
} }
bool SupportTreeBuilder::build(const SupportableMesh &sm) }} // namespace Slic3r::sla
{
ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm;
return SupportTreeBuildsteps::execute(*this, sm);
}
}
}

View File

@ -2,7 +2,6 @@
#define SLA_SUPPORTTREEBUILDER_HPP #define SLA_SUPPORTTREEBUILDER_HPP
#include <libslic3r/SLA/Concurrency.hpp> #include <libslic3r/SLA/Concurrency.hpp>
#include <libslic3r/SLA/Common.hpp>
#include <libslic3r/SLA/SupportTree.hpp> #include <libslic3r/SLA/SupportTree.hpp>
#include <libslic3r/SLA/Contour3D.hpp> #include <libslic3r/SLA/Contour3D.hpp>
#include <libslic3r/SLA/Pad.hpp> #include <libslic3r/SLA/Pad.hpp>
@ -50,13 +49,6 @@ namespace sla {
* nearby pillar. * 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) { template<class Vec> double distance(const Vec& p) {
return std::sqrt(p.transpose() * 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); return distance(p);
} }
Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI), const Vec3d DOWN = {0.0, 0.0, -1.0};
double fa=(2*PI/360));
// Down facing cylinder in Z direction with arguments: struct SupportTreeNode
// r: radius {
// h: Height static const constexpr long ID_UNSET = -1;
// 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});
const constexpr long ID_UNSET = -1; long id = ID_UNSET; // For identification withing a tree.
};
struct Head { // A pinhead originating from a support point
Contour3D mesh; struct Head: public SupportTreeNode {
Vec3d dir = DOWN;
size_t steps = 45; Vec3d pos = {0, 0, 0};
Vec3d dir = {0, 0, -1};
Vec3d tr = {0, 0, 0};
double r_back_mm = 1; double r_back_mm = 1;
double r_pin_mm = 0.5; double r_pin_mm = 0.5;
double width_mm = 2; double width_mm = 2;
double penetration_mm = 0.5; 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. // If there is a pillar connecting to this head, then the id will be set.
long pillar_id = ID_UNSET; long pillar_id = ID_UNSET;
@ -106,31 +90,23 @@ struct Head {
double r_small_mm, double r_small_mm,
double length_mm, double length_mm,
double penetration, double penetration,
const Vec3d &direction = {0, 0, -1}, // direction (normal to the dull end) const Vec3d &direction = DOWN, // direction (normal to the dull end)
const Vec3d &offset = {0, 0, 0}, // displacement const Vec3d &offset = {0, 0, 0} // displacement
const size_t circlesteps = 45); );
void transform() inline double real_width() const
{ {
using Quaternion = Eigen::Quaternion<double>; return 2 * r_pin_mm + width_mm + 2 * r_back_mm ;
// 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;
} }
inline double fullwidth() const 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 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 inline double request_pillar_radius(double radius) const
@ -140,31 +116,17 @@ struct Head {
} }
}; };
struct Junction { // A junction connecting bridges and pillars
Contour3D mesh; struct Junction: public SupportTreeNode {
double r = 1; double r = 1;
size_t steps = 45;
Vec3d pos; Vec3d pos;
long id = ID_UNSET; Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {}
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;
}
}; };
struct Pillar { struct Pillar: public SupportTreeNode {
Contour3D mesh; double height, r;
Contour3D base;
double r = 1;
size_t steps = 0;
Vec3d endpt; Vec3d endpt;
double height = 0;
long id = ID_UNSET;
// If the pillar connects to a head, this is the id of that head // 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 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 // How many pillars are cascaded with this one
unsigned links = 0; unsigned links = 0;
Pillar(const Vec3d& jp, const Vec3d& endp, Pillar(const Vec3d &endp, double h, double radius = 1.):
double radius = 1, size_t st = 45); height{h}, r(radius), endpt(endp), starts_from_head(false) {}
Pillar(const Junction &junc, const Vec3d &endp) Vec3d startpoint() const
: 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
{ {
return {endpt(X), endpt(Y), endpt(Z) + height}; return {endpt.x(), endpt.y(), endpt.z() + height};
} }
inline const Vec3d& endpoint() const { return endpt; } const Vec3d& endpoint() const { return endpt; }
Pillar& add_base(double baseheight = 3, double radius = 2);
}; };
// 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) // A Bridge between two pillars (with junction endpoints)
struct Bridge { struct Bridge: public SupportTreeNode {
Contour3D mesh;
double r = 0.8; double r = 0.8;
long id = ID_UNSET;
Vec3d startp = Vec3d::Zero(), endp = Vec3d::Zero(); Vec3d startp = Vec3d::Zero(), endp = Vec3d::Zero();
Bridge(const Vec3d &j1, Bridge(const Vec3d &j1,
const Vec3d &j2, const Vec3d &j2,
double r_mm = 0.8, double r_mm = 0.8): r{r_mm}, startp{j1}, endp{j2}
size_t steps = 45); {}
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 struct DiffBridge: public Bridge {
// edges on the endpoints. Used for headless support points. double end_r;
struct CompactBridge {
Contour3D mesh; DiffBridge(const Vec3d &p_s, const Vec3d &p_e, double r_s, double r_e)
long id = ID_UNSET; : Bridge{p_s, p_e, r_s}, end_r{r_e}
{}
CompactBridge(const Vec3d& sp,
const Vec3d& ep,
const Vec3d& n,
double r,
bool endball = true,
size_t steps = 45);
}; };
// A wrapper struct around the pad // A wrapper struct around the pad
@ -258,13 +218,16 @@ struct Pad {
// merged mesh. It can be retrieved using a dedicated method (pad()) // merged mesh. It can be retrieved using a dedicated method (pad())
class SupportTreeBuilder: public SupportTree { class SupportTreeBuilder: public SupportTree {
// For heads it is beneficial to use the same IDs as for the support points. // For heads it is beneficial to use the same IDs as for the support points.
std::vector<Head> m_heads; std::vector<Head> m_heads;
std::vector<size_t> m_head_indices; std::vector<size_t> m_head_indices;
std::vector<Pillar> m_pillars; std::vector<Pillar> m_pillars;
std::vector<Junction> m_junctions; std::vector<Junction> m_junctions;
std::vector<Bridge> m_bridges; std::vector<Bridge> m_bridges;
std::vector<Bridge> m_crossbridges; std::vector<Bridge> m_crossbridges;
std::vector<CompactBridge> m_compact_bridges; std::vector<DiffBridge> m_diffbridges;
std::vector<Pedestal> m_pedestals;
std::vector<Anchor> m_anchors;
Pad m_pad; Pad m_pad;
using Mutex = ccr::SpinningMutex; using Mutex = ccr::SpinningMutex;
@ -274,8 +237,8 @@ class SupportTreeBuilder: public SupportTree {
mutable bool m_meshcache_valid = false; mutable bool m_meshcache_valid = false;
mutable double m_model_height = 0; // the full height of the model mutable double m_model_height = 0; // the full height of the model
template<class...Args> template<class BridgeT, class...Args>
const Bridge& _add_bridge(std::vector<Bridge> &br, Args&&... args) const BridgeT& _add_bridge(std::vector<BridgeT> &br, Args&&... args)
{ {
std::lock_guard<Mutex> lk(m_mutex); std::lock_guard<Mutex> lk(m_mutex);
br.emplace_back(std::forward<Args>(args)...); br.emplace_back(std::forward<Args>(args)...);
@ -306,7 +269,7 @@ public:
return m_heads.back(); 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); std::lock_guard<Mutex> lk(m_mutex);
if (m_pillars.capacity() < m_heads.size()) if (m_pillars.capacity() < m_heads.size())
@ -315,7 +278,9 @@ public:
assert(headid >= 0 && size_t(headid) < m_head_indices.size()); assert(headid >= 0 && size_t(headid) < m_head_indices.size());
Head &head = m_heads[m_head_indices[size_t(headid)]]; 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& pillar = m_pillars.back();
pillar.id = long(m_pillars.size() - 1); pillar.id = long(m_pillars.size() - 1);
head.pillar_id = pillar.id; head.pillar_id = pillar.id;
@ -326,11 +291,15 @@ public:
return pillar.id; 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); std::lock_guard<Mutex> lk(m_mutex);
assert(pid >= 0 && size_t(pid) < m_pillars.size()); m_anchors.emplace_back(std::forward<Args>(args)...);
m_pillars[size_t(pid)].add_base(baseheight, radius); m_anchors.back().id = long(m_junctions.size() - 1);
m_meshcache_valid = false;
return m_anchors.back();
} }
void increment_bridges(const Pillar& pillar) void increment_bridges(const Pillar& pillar)
@ -371,17 +340,6 @@ public:
return pillar.id; 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) template<class...Args> const Junction& add_junction(Args&&... args)
{ {
std::lock_guard<Mutex> lk(m_mutex); std::lock_guard<Mutex> lk(m_mutex);
@ -391,18 +349,18 @@ public:
return m_junctions.back(); 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); std::lock_guard<Mutex> lk(m_mutex);
assert(headid >= 0 && size_t(headid) < m_head_indices.size()); assert(headid >= 0 && size_t(headid) < m_head_indices.size());
Head &h = m_heads[m_head_indices[size_t(headid)]]; 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); m_bridges.back().id = long(m_bridges.size() - 1);
h.bridge_id = m_bridges.back().id; h.bridge_id = m_bridges.back().id;
@ -414,14 +372,10 @@ public:
{ {
return _add_bridge(m_crossbridges, std::forward<Args>(args)...); 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); return _add_bridge(m_diffbridges, std::forward<Args>(args)...);
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();
} }
Head &head(unsigned id) Head &head(unsigned id)
@ -439,7 +393,7 @@ public:
} }
inline const std::vector<Pillar> &pillars() const { return m_pillars; } 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> &bridges() const { return m_bridges; }
inline const std::vector<Bridge> &crossbridges() const { return m_crossbridges; } inline const std::vector<Bridge> &crossbridges() const { return m_crossbridges; }
@ -464,7 +418,7 @@ public:
const Pad& pad() const { return m_pad; } const Pad& pad() const { return m_pad; }
// WITHOUT THE PAD!!! // WITHOUT THE PAD!!!
const TriangleMesh &merged_mesh() const; const TriangleMesh &merged_mesh(size_t steps = 45) const;
// WITH THE PAD // WITH THE PAD
double full_height() const; double full_height() const;
@ -488,8 +442,6 @@ public:
virtual const TriangleMesh &retrieve_mesh( virtual const TriangleMesh &retrieve_mesh(
MeshType meshtype = MeshType::Support) const override; MeshType meshtype = MeshType::Support) const override;
bool build(const SupportableMesh &supportable_mesh);
}; };
}} // namespace Slic3r::sla }} // namespace Slic3r::sla

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@
#include <libslic3r/SLA/SupportTreeBuilder.hpp> #include <libslic3r/SLA/SupportTreeBuilder.hpp>
#include <libslic3r/SLA/Clustering.hpp> #include <libslic3r/SLA/Clustering.hpp>
#include <libslic3r/SLA/SpatIndex.hpp>
namespace Slic3r { namespace Slic3r {
namespace sla { namespace sla {
@ -16,9 +17,7 @@ enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers
X, Y, Z X, Y, Z
}; };
inline Vec2d to_vec2(const Vec3d& v3) { inline Vec2d to_vec2(const Vec3d &v3) { return {v3(X), v3(Y)}; }
return {v3(X), v3(Y)};
}
inline std::pair<double, double> dir_to_spheric(const Vec3d &n, double norm = 1.) 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); return spheric_to_dir(v.first, v.second);
} }
// This function returns the position of the centroid in the input 'clust' inline Vec3d spheric_to_dir(const std::array<double, 2> &v)
// vector of point indices.
template<class DistFn>
long cluster_centroid(const ClusterEl& clust,
const std::function<Vec3d(size_t)> &pointfn,
DistFn df)
{ {
switch(clust.size()) { return spheric_to_dir(v[0], v[1]);
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; // Give points on a 3D ring with given center, radius and orientation
default: ; // 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 public:
// 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 PointRing(const Vec3d &n)
// for small clusters only (cca 3 elements) {
m_phis = linspace_array<N>(0., 2 * PI);
std::vector<bool> sel(clust.size(), false); // create full zero bitmask // We have to address the case when the direction vector v (same as
std::fill(sel.end() - 2, sel.end(), true); // insert the two ones // dir) is coincident with one of the world axes. In this case two of
std::vector<double> avgs(clust.size(), 0.0); // store the average distances // 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 { Vec3d get(size_t idx, const Vec3d src, double r) const
std::array<size_t, 2> idx; {
for(size_t i = 0, j = 0; i < clust.size(); i++) if(sel[i]) idx[j++] = i; double phi = m_phis[idx];
double sinphi = std::sin(phi);
double cosphi = std::cos(phi);
double d = df(pointfn(clust[idx[0]]), double rpscos = r * cosphi;
pointfn(clust[idx[1]])); double rpssin = r * sinphi;
// add the distance to the sums for both associated points // Point on the sphere
for(auto i : idx) avgs[i] += d; 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 //IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan(""));
} while(std::next_permutation(sel.begin(), sel.end())); //IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan(""));
// 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());
}
inline Vec3d dirv(const Vec3d& startp, const Vec3d& endp) { inline Vec3d dirv(const Vec3d& startp, const Vec3d& endp) {
return (endp - startp).normalized(); return (endp - startp).normalized();
@ -170,8 +185,8 @@ IntegerOnly<DoubleI> pairhash(I a, I b)
} }
class SupportTreeBuildsteps { class SupportTreeBuildsteps {
const SupportConfig& m_cfg; const SupportTreeConfig& m_cfg;
const EigenMesh3D& m_mesh; const IndexedMesh& m_mesh;
const std::vector<SupportPoint>& m_support_pts; const std::vector<SupportPoint>& m_support_pts;
using PtIndices = std::vector<unsigned>; using PtIndices = std::vector<unsigned>;
@ -180,7 +195,7 @@ class SupportTreeBuildsteps {
PtIndices m_iheads_onmodel; PtIndices m_iheads_onmodel;
PtIndices m_iheadless; // headless support points 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. // normals for support points from model faces.
PointSet m_support_nmls; PointSet m_support_nmls;
@ -206,7 +221,7 @@ class SupportTreeBuildsteps {
// When bridging heads to pillars... TODO: find a cleaner solution // When bridging heads to pillars... TODO: find a cleaner solution
ccr::BlockingMutex m_bridge_mutex; 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) const Vec3d& dir)
{ {
return m_mesh.query_ray_hit(s, 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 // 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 // with a zero distance value instead of a NAN. This way the result can
// be used safely for comparison with other distances. // 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& s,
const Vec3d& dir, const Vec3d& dir,
double r_pin, double r_pin,
double r_back, double r_back,
double width); double width,
double safety_d);
template<class...Args>
inline double pinhead_mesh_distance(Args&&...args) { IndexedMesh::hit_result pinhead_mesh_intersect(
return pinhead_mesh_intersect(std::forward<Args>(args)...).distance(); 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. // 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 // 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 // with a zero distance value instead of a NAN. This way the result can
// be used safely for comparison with other distances. // 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& s,
const Vec3d& dir, const Vec3d& dir,
double r, 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> template<class...Args>
inline double bridge_mesh_distance(Args&&...args) { inline double bridge_mesh_distance(Args&&...args) {
@ -268,20 +301,29 @@ class SupportTreeBuildsteps {
inline bool connect_to_ground(Head& head); inline bool connect_to_ground(Head& head);
bool connect_to_model_body(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 // This is a proxy function for pillar creation which will mind the gap
// between the pad and the model bottom in zero elevation mode. // between the pad and the model bottom in zero elevation mode.
// jp is the starting junction point which needs to be routed down. // jp is the starting junction point which needs to be routed down.
// sourcedir is the allowed direction of an optional bridge between the // sourcedir is the allowed direction of an optional bridge between the
// jp junction and the final pillar. // jp junction and the final pillar.
void create_ground_pillar(const Vec3d &jp, bool create_ground_pillar(const Vec3d &jp,
const Vec3d &sourcedir, const Vec3d &sourcedir,
double radius, 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: public:
SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm); SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm);
@ -324,11 +366,6 @@ public:
void interconnect_pillars(); 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(); } inline void merge_result() { m_builder.merged_mesh(); }
static bool execute(SupportTreeBuilder & builder, const SupportableMesh &sm); static bool execute(SupportTreeBuilder & builder, const SupportableMesh &sm);

View 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

View 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

View File

@ -35,13 +35,16 @@ bool is_zero_elevation(const SLAPrintObjectConfig &c)
} }
// Compile the argument for support creation from the static print config. // 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.enabled = c.supports_enable.getBool();
scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat(); 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_penetration_mm = c.support_head_penetration.getFloat();
scfg.head_width_mm = c.support_head_width.getFloat(); scfg.head_width_mm = c.support_head_width.getFloat();
scfg.object_elevation_mm = is_zero_elevation(c) ? scfg.object_elevation_mm = is_zero_elevation(c) ?
@ -616,7 +619,7 @@ std::string SLAPrint::validate() const
return L("Cannot proceed without support points! " return L("Cannot proceed without support points! "
"Add support points or disable support generation."); "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; 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_penetration"
|| opt_key == "support_head_width" || opt_key == "support_head_width"
|| opt_key == "support_pillar_diameter" || opt_key == "support_pillar_diameter"
|| opt_key == "support_small_pillar_diameter_percent"
|| opt_key == "support_max_bridges_on_pillar" || opt_key == "support_max_bridges_on_pillar"
|| opt_key == "support_pillar_connection_mode" || opt_key == "support_pillar_connection_mode"
|| opt_key == "support_buildplate_only" || opt_key == "support_buildplate_only"

View File

@ -544,7 +544,7 @@ private:
bool is_zero_elevation(const SLAPrintObjectConfig &c); 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); sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c);

View File

@ -360,18 +360,6 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
// removed them on purpose. No calculation will be done. // removed them on purpose. No calculation will be done.
po.m_supportdata->pts = po.transformed_support_points(); 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) void SLAPrint::Steps::support_tree(SLAPrintObject &po)
@ -382,6 +370,13 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po)
if (pcfg.embed_object) if (pcfg.embed_object)
po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm); 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->cfg = make_support_cfg(po.m_config);
// po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points()); // po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());

View File

@ -169,6 +169,8 @@ set(SLIC3R_GUI_SOURCES
GUI/InstanceCheck.hpp GUI/InstanceCheck.hpp
GUI/Search.cpp GUI/Search.cpp
GUI/Search.hpp GUI/Search.hpp
GUI/NotificationManager.cpp
GUI/NotificationManager.hpp
Utils/Http.cpp Utils/Http.cpp
Utils/Http.hpp Utils/Http.hpp
Utils/FixModelByWin10.cpp Utils/FixModelByWin10.cpp

View File

@ -91,15 +91,17 @@ void BackgroundSlicingProcess::process_fff()
{ {
assert(m_print == m_fff_print); assert(m_print == m_fff_print);
m_print->process(); 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 #if ENABLE_GCODE_VIEWER
m_fff_print->export_gcode(m_temp_output_path, m_gcode_result, m_thumbnail_cb); m_fff_print->export_gcode(m_temp_output_path, m_gcode_result, m_thumbnail_cb);
#else #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 #endif // ENABLE_GCODE_VIEWER
if (this->set_step_started(bspsGCodeFinalize)) { if (this->set_step_started(bspsGCodeFinalize)) {
if (! m_export_path.empty()) { if (! m_export_path.empty()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
//FIXME localize the messages //FIXME localize the messages
// Perform the final post-processing of the export path by applying the print statistics over the file name. // 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); 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()); 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()); m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str());
} else if (! m_upload_job.empty()) { } else if (! m_upload_job.empty()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
prepare_upload(); prepare_upload();
} else { } else {
m_print->set_status(100, _utf8(L("Slicing complete"))); m_print->set_status(100, _utf8(L("Slicing complete")));
@ -155,6 +158,8 @@ void BackgroundSlicingProcess::process_sla()
m_print->process(); m_print->process();
if (this->set_step_started(bspsGCodeFinalize)) { if (this->set_step_started(bspsGCodeFinalize)) {
if (! m_export_path.empty()) { 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); const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path);
Zipper zipper(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()); m_print->set_status(100, (boost::format(_utf8(L("Masked SLA file exported to %1%"))) % export_path).str());
} else if (! m_upload_job.empty()) { } else if (! m_upload_job.empty()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
prepare_upload(); prepare_upload();
} else { } else {
m_print->set_status(100, _utf8(L("Slicing complete"))); m_print->set_status(100, _utf8(L("Slicing complete")));

View File

@ -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 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. // 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; } 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. // Activate either m_fff_print or m_sla_print.
// Return true if changed. // Return true if changed.
@ -204,6 +208,9 @@ private:
int m_event_slicing_completed_id = 0; int m_event_slicing_completed_id = 0;
// wxWidgets command ID to be sent to the plater to inform that the task finished. // wxWidgets command ID to be sent to the plater to inform that the task finished.
int m_event_finished_id = 0; 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 }; // namespace Slic3r

View File

@ -353,6 +353,7 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config)
toggle_field("support_head_penetration", supports_en); toggle_field("support_head_penetration", supports_en);
toggle_field("support_head_width", supports_en); toggle_field("support_head_width", supports_en);
toggle_field("support_pillar_diameter", 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_max_bridges_on_pillar", supports_en);
toggle_field("support_pillar_connection_mode", supports_en); toggle_field("support_pillar_connection_mode", supports_en);
toggle_field("support_buildplate_only", supports_en); toggle_field("support_buildplate_only", supports_en);

View File

@ -33,6 +33,7 @@
#include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectManipulation.hpp"
#include "Mouse3DController.hpp" #include "Mouse3DController.hpp"
#include "I18N.hpp" #include "I18N.hpp"
#include "NotificationManager.hpp"
#if ENABLE_RETINA_GL #if ENABLE_RETINA_GL
#include "slic3r/Utils/RetinaHelper.hpp" #include "slic3r/Utils/RetinaHelper.hpp"
@ -627,19 +628,45 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool
m_warnings.emplace_back(warning); m_warnings.emplace_back(warning);
std::sort(m_warnings.begin(), m_warnings.end()); 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 { else {
if (it == m_warnings.end()) // deactivating something that is not active is an easy task if (it == m_warnings.end()) // deactivating something that is not active is an easy task
return; return;
m_warnings.erase(it); 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(); reset();
m_msg_text = "";// save information for rescaling m_msg_text = "";// save information for rescaling
return; return;
} }*/
} }
/*
// Look at the end of our vector and generate proper texture. // Look at the end of our vector and generate proper texture.
std::string text; std::string text;
bool red_colored = false; bool red_colored = false;
@ -661,6 +688,7 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool
// save information for rescaling // save information for rescaling
m_msg_text = text; m_msg_text = text;
m_is_colored_red = red_colored; m_is_colored_red = red_colored;
*/
} }
@ -2075,6 +2103,8 @@ void GLCanvas3D::render()
std::string tooltip; std::string tooltip;
// Negative coordinate means out of the window, likely because the window was deactivated. // Negative coordinate means out of the window, likely because the window was deactivated.
// In that case the tooltip should be hidden. // In that case the tooltip should be hidden.
if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.) 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); m_tooltip.render(m_mouse.position, *this);
wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this); wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this);
wxGetApp().plater()->get_notification_manager()->render_notifications(*this);
wxGetApp().imgui()->render(); wxGetApp().imgui()->render();
@ -3517,9 +3549,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS #ifdef SLIC3R_DEBUG_MOUSE_EVENTS
printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str()); printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str());
#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ #endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
#if ENABLE_GCODE_VIEWER m_dirty = true;
m_dirty = true;
#endif // ENABLE_GCODE_VIEWER
// do not return if dragging or tooltip not empty to allow for tooltip update // do not return if dragging or tooltip not empty to allow for tooltip update
if (!m_mouse.dragging && m_tooltip.is_empty()) if (!m_mouse.dragging && m_tooltip.is_empty())
return; return;
@ -3916,7 +3946,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_dirty = true; m_dirty = true;
#else #else
// Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor hovers over. // 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; m_dirty = true;
#endif // ENABLE_GCODE_VIEWER #endif // ENABLE_GCODE_VIEWER
} }

View File

@ -54,6 +54,7 @@
#include "Mouse3DController.hpp" #include "Mouse3DController.hpp"
#include "RemovableDriveManager.hpp" #include "RemovableDriveManager.hpp"
#include "InstanceCheck.hpp" #include "InstanceCheck.hpp"
#include "NotificationManager.hpp"
#ifdef __WXMSW__ #ifdef __WXMSW__
#include <dbt.h> #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 // supplied as argument to --datadir; in that case we should still run the wizard
preset_bundle->setup_directories(); preset_bundle->setup_directories();
#ifdef __WXMSW__ #ifdef __WXMSW__
associate_3mf_files(); associate_3mf_files();
#endif // __WXMSW__ #endif // __WXMSW__
@ -392,6 +393,11 @@ bool GUI_App::on_init_inner()
Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent &evt) { Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent &evt) {
app_config->set("version_online", into_u8(evt.GetString())); app_config->set("version_online", into_u8(evt.GetString()));
app_config->save(); 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 // initialize label colors and fonts
@ -1453,7 +1459,7 @@ void GUI_App::check_updates(const bool verbose)
PresetUpdater::UpdateResult updater_result; PresetUpdater::UpdateResult updater_result;
try { 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) { if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) {
mainframe->Close(); mainframe->Close();
} }

View File

@ -198,12 +198,15 @@ public:
Plater* plater(); Plater* plater();
Model& model(); Model& model();
AppConfig* app_config{ nullptr }; AppConfig* app_config{ nullptr };
PresetBundle* preset_bundle{ nullptr }; PresetBundle* preset_bundle{ nullptr };
PresetUpdater* preset_updater{ nullptr }; PresetUpdater* preset_updater{ nullptr };
MainFrame* mainframe{ nullptr }; MainFrame* mainframe{ nullptr };
Plater* plater_{ nullptr }; Plater* plater_{ nullptr };
PresetUpdater* get_preset_updater() { return preset_updater; }
wxNotebook* tab_panel() const ; wxNotebook* tab_panel() const ;
int extruders_cnt() const; int extruders_cnt() const;
int extruders_edited_cnt() const; int extruders_edited_cnt() const;

View File

@ -37,11 +37,17 @@ namespace GUI {
static const std::map<const char, std::string> font_icons = { static const std::map<const char, std::string> font_icons = {
{ImGui::PrintIconMarker , "cog" }, {ImGui::PrintIconMarker , "cog" },
{ImGui::PrinterIconMarker , "printer" }, {ImGui::PrinterIconMarker , "printer" },
{ImGui::PrinterSlaIconMarker, "sla_printer"}, {ImGui::PrinterSlaIconMarker, "sla_printer" },
{ImGui::FilamentIconMarker , "spool" }, {ImGui::FilamentIconMarker , "spool" },
{ImGui::MaterialIconMarker , "resin" } {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 }; 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); 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) bool ImGuiWrapper::begin(const std::string &name, int flags)
{ {
return ImGui::Begin(name.c_str(), nullptr, (ImGuiWindowFlags)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()); 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) bool ImGuiWrapper::radio_button(const wxString &label, bool active)
{ {
auto label_utf8 = into_u8(label); auto label_utf8 = into_u8(label);
return ImGui::RadioButton(label_utf8.c_str(), active); 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) 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()); return ImGui::InputDouble(label.c_str(), const_cast<double*>(&value), 0.0f, 0.0f, format.c_str());

View File

@ -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_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_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 std::string &name, int flags = 0);
bool begin(const wxString &name, int flags = 0); bool begin(const wxString &name, int flags = 0);
@ -65,7 +66,9 @@ public:
void end(); void end();
bool button(const wxString &label); bool button(const wxString &label);
bool button(const wxString& label, float width, float height);
bool radio_button(const wxString &label, bool active); 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 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_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"); bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f");

View File

@ -134,7 +134,7 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
Vec3d direction; Vec3d direction;
line_from_mouse_pos(mouse_pos, trafo, camera, point, 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()) if (hits.empty())
return false; // no intersection found return false; // no intersection found
@ -184,7 +184,7 @@ std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo
bool is_obscured = false; bool is_obscured = false;
// Cast a ray in the direction of the camera and look for intersection with the mesh: // 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. // 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>(), hits = m_emesh.query_ray_hits((inverse_trafo * pt + direction_to_camera_mesh * EPSILON).cast<double>(),
direction_to_camera.cast<double>()); direction_to_camera.cast<double>());

View File

@ -3,7 +3,7 @@
#include "libslic3r/Point.hpp" #include "libslic3r/Point.hpp"
#include "libslic3r/Geometry.hpp" #include "libslic3r/Geometry.hpp"
#include "libslic3r/SLA/EigenMesh3D.hpp" #include "libslic3r/SLA/IndexedMesh.hpp"
#include "admesh/stl.h" #include "admesh/stl.h"
#include "slic3r/GUI/3DScene.hpp" #include "slic3r/GUI/3DScene.hpp"
@ -147,7 +147,7 @@ public:
Vec3f get_triangle_normal(size_t facet_idx) const; Vec3f get_triangle_normal(size_t facet_idx) const;
private: private:
sla::EigenMesh3D m_emesh; sla::IndexedMesh m_emesh;
std::vector<stl_normal> m_normals; std::vector<stl_normal> m_normals;
}; };

View File

@ -7,6 +7,7 @@
#include "AppConfig.hpp" #include "AppConfig.hpp"
#include "GLCanvas3D.hpp" #include "GLCanvas3D.hpp"
#include "Plater.hpp" #include "Plater.hpp"
#include "NotificationManager.hpp"
#include <wx/glcanvas.h> #include <wx/glcanvas.h>
@ -403,6 +404,8 @@ void Mouse3DController::disconnected()
m_params_by_device[m_device_str] = m_params_ui; m_params_by_device[m_device_str] = m_params_ui;
m_device_str.clear(); m_device_str.clear();
m_connected = false; m_connected = false;
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::Mouse3dDisconnected, *(wxGetApp().plater()->get_current_canvas3D()));
wxGetApp().plater()->CallAfter([]() { wxGetApp().plater()->CallAfter([]() {
Plater *plater = wxGetApp().plater(); Plater *plater = wxGetApp().plater();
if (plater != nullptr) { if (plater != nullptr) {

View 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 &notification_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

View 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_

View File

@ -79,8 +79,10 @@
#include "../Utils/PrintHost.hpp" #include "../Utils/PrintHost.hpp"
#include "../Utils/FixModelByWin10.hpp" #include "../Utils/FixModelByWin10.hpp"
#include "../Utils/UndoRedo.hpp" #include "../Utils/UndoRedo.hpp"
#include "../Utils/PresetUpdater.hpp"
#include "RemovableDriveManager.hpp" #include "RemovableDriveManager.hpp"
#include "InstanceCheck.hpp" #include "InstanceCheck.hpp"
#include "NotificationManager.hpp"
#ifdef __APPLE__ #ifdef __APPLE__
#include "Gizmos/GLGizmosManager.hpp" #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_UPDATE, SlicingStatusEvent);
wxDEFINE_EVENT(EVT_SLICING_COMPLETED, wxCommandEvent); wxDEFINE_EVENT(EVT_SLICING_COMPLETED, wxCommandEvent);
wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, wxCommandEvent); wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, wxCommandEvent);
wxDEFINE_EVENT(EVT_EXPORT_BEGAN, wxCommandEvent);
// Sidebar widgets // Sidebar widgets
@ -720,7 +723,7 @@ struct Sidebar::priv
wxButton *btn_export_gcode; wxButton *btn_export_gcode;
wxButton *btn_reslice; wxButton *btn_reslice;
ScalableButton *btn_send_gcode; 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) ScalableButton* btn_export_gcode_removable; //exports to removable drives (appears only if removable drive is connected)
bool is_collapsed {false}; 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_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"); 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" // 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) { auto init_btn = [this](wxButton **btn, wxString label, const int button_height) {
*btn = new wxButton(this, wxID_ANY, label, wxDefaultPosition, *btn = new wxButton(this, wxID_ANY, label, wxDefaultPosition,
wxSize(-1, button_height), wxBU_EXACTFIT); 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_export_gcode, 1, wxEXPAND);
complect_btns_sizer->Add(p->btn_send_gcode); complect_btns_sizer->Add(p->btn_send_gcode);
complect_btns_sizer->Add(p->btn_export_gcode_removable); 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); 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->plater->select_view_3D("Preview");
}); });
p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); }); 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); }); 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->object_info->msw_rescale();
p->btn_send_gcode->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(); 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_export_gcode->SetMinSize(wxSize(-1, scaled_height));
p->btn_reslice ->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 // btn...->msw_rescale() updates icon on button, so use it
p->btn_send_gcode->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(); p->btn_export_gcode_removable->msw_rescale();
p->scrolled->Layout(); p->scrolled->Layout();
@ -1402,6 +1405,12 @@ void Sidebar::update_sliced_info_sizer()
new_label += format_wxstr("\n - %1%", _L("normal mode")); new_label += format_wxstr("\n - %1%", _L("normal mode"));
info_text += format_wxstr("\n%1%", ps.estimated_normal_print_time); info_text += format_wxstr("\n%1%", ps.estimated_normal_print_time);
fill_labels(ps.estimated_normal_custom_gcode_print_times, new_label, info_text); 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") { if (ps.estimated_silent_print_time != "N/A") {
new_label += format_wxstr("\n - %1%", _L("stealth mode")); 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_reslice->Enable(enable);
p->btn_export_gcode->Enable(enable); p->btn_export_gcode->Enable(enable);
p->btn_send_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); p->btn_export_gcode_removable->Enable(enable);
} }
bool Sidebar::show_reslice(bool show) const { return p->btn_reslice->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_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_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_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() bool Sidebar::is_multifilament()
{ {
@ -1651,6 +1661,7 @@ struct Plater::priv
GLToolbar view_toolbar; GLToolbar view_toolbar;
GLToolbar collapse_toolbar; GLToolbar collapse_toolbar;
Preview *preview; Preview *preview;
NotificationManager* notification_manager;
BackgroundSlicingProcess background_process; BackgroundSlicingProcess background_process;
bool suppressed_backround_processing_update { false }; bool suppressed_backround_processing_update { false };
@ -1844,7 +1855,17 @@ struct Plater::priv
void on_slicing_update(SlicingStatusEvent&); void on_slicing_update(SlicingStatusEvent&);
void on_slicing_completed(wxCommandEvent&); void on_slicing_completed(wxCommandEvent&);
void on_process_completed(wxCommandEvent&); void on_process_completed(wxCommandEvent&);
void on_export_began(wxCommandEvent&);
void on_layer_editing_toggled(bool enable); 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_add(SimpleEvent&);
void on_action_split_objects(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. // 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 writing_to_removable_device = { false };
bool inside_snapshot_capture() { return m_prevent_snapshots != 0; } bool inside_snapshot_capture() { return m_prevent_snapshots != 0; }
bool process_completed_with_error { false };
private: private:
bool init_object_menu(); bool init_object_menu();
bool init_common_menu(wxMenu* menu, const bool is_part = false); 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_fff_printer_profile_name;
std::string m_last_sla_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); 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_slicing_completed_event(EVT_SLICING_COMPLETED);
background_process.set_finished_event(EVT_PROCESS_COMPLETED); background_process.set_finished_event(EVT_PROCESS_COMPLETED);
background_process.set_export_began_event(EVT_EXPORT_BEGAN);
// Default printer technology for default config. // Default printer technology for default config.
background_process.select_technology(this->printer_technology); background_process.select_technology(this->printer_technology);
// Register progress callback from the Print class to the Plater. // 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); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_double_slider(evt); });
#endif // ENABLE_GCODE_VIEWER #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_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_3D, [q](SimpleEvent&) { q->select_view_3D("3D"); });
q->Bind(EVT_GLVIEWTOOLBAR_PREVIEW, [q](SimpleEvent&) { q->select_view_3D("Preview"); }); 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 */ #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) { if (evt.data.second) {
this->show_action_buttons(this->ready_to_slice); 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."), 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),
evt.data.first.name, evt.data.first.path)); NotificationManager::NotificationLevel::RegularNotification, *q->get_current_canvas3D());
} else } else {
Slic3r::GUI::show_info(this->q, format_wxstr(_L("Ejecting of device %s(%s) has failed."), notification_manager->push_notification(format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path),
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. // Start the background thread and register this window as a target for update events.
wxGetApp().removable_drive_manager()->init(this->q); wxGetApp().removable_drive_manager()->init(this->q);
#ifdef _WIN32 #ifdef _WIN32
@ -2753,6 +2792,8 @@ void Plater::priv::reset()
{ {
Plater::TakeSnapshot snapshot(q, _L("Reset Project")); Plater::TakeSnapshot snapshot(q, _L("Reset Project"));
clear_warnings();
set_project_filename(wxEmptyString); set_project_filename(wxEmptyString);
#if !ENABLE_GCODE_VIEWER #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. // 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(); std::string err = this->background_process.validate();
if (err.empty()) { if (err.empty()) {
notification_manager->set_all_slicing_errors_gray(true);
if (invalidated != Print::APPLY_STATUS_UNCHANGED && this->background_processing_enabled()) if (invalidated != Print::APPLY_STATUS_UNCHANGED && this->background_processing_enabled())
return_state |= UPDATE_BACKGROUND_PROCESS_RESTART; return_state |= UPDATE_BACKGROUND_PROCESS_RESTART;
} else { } else {
// The print is not valid. // The print is not valid.
// Only show the error message immediately, if the top level parent of this window is active. // Show error as notification.
auto p = dynamic_cast<wxWindow*>(this->q); notification_manager->push_slicing_error_notification(err, *q->get_current_canvas3D());
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;
}
return_state |= UPDATE_BACKGROUND_PROCESS_INVALID; return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
} }
} else if (! this->delayed_error_message.empty()) { } 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; 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() && if (invalidated != Print::APPLY_STATUS_UNCHANGED && was_running && ! this->background_process.running() &&
(return_state & UPDATE_BACKGROUND_PROCESS_RESTART) == 0) { (return_state & UPDATE_BACKGROUND_PROCESS_RESTART) == 0) {
// The background processing was killed and it will not be restarted. // 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->statusbar()->set_status_text(_L("Cancelling"));
this->background_process.stop(); this->background_process.stop();
}); });
if (!show_warning_dialog)
on_slicing_began();
return true; 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) if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
return; return;
show_warning_dialog = true;
if (! output_path.empty()) { if (! output_path.empty()) {
background_process.schedule_export(output_path.string(), output_path_on_removable_media); background_process.schedule_export(output_path.string(), output_path_on_removable_media);
} else { } 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)); state = print_object->step_state_with_warnings(static_cast<SLAPrintObjectStep>(warning_step));
} }
// Now process state.warnings. // 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) { switch (this->printer_technology) {
case ptFFF: case ptFFF:
this->update_fff_scene(); this->update_fff_scene();
@ -3544,8 +3596,63 @@ void Plater::priv::on_slicing_completed(wxCommandEvent &)
break; break;
default: 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) void Plater::priv::on_process_completed(wxCommandEvent &evt)
{ {
// Stop the background task, wait until the thread goes into the "Idle" state. // 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) { if (error) {
wxString message = evt.GetString(); wxString message = evt.GetString();
if (message.IsEmpty()) if (message.IsEmpty())
message = _L("Export failed"); message = _L("Export failed.");
if (q->m_tracking_popup_menu) notification_manager->push_slicing_error_notification(boost::nowide::narrow(message), *q->get_current_canvas3D());
// 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);
this->statusbar()->set_status_text(message); 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) if (canceled)
this->statusbar()->set_status_text(_L("Cancelled")); this->statusbar()->set_status_text(_L("Cancelled"));
@ -3597,18 +3703,21 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt)
default: break; default: break;
} }
if (canceled) { if (canceled) {
if (wxGetApp().get_mode() == comSimple) if (wxGetApp().get_mode() == comSimple)
sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now"); sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now");
show_action_buttons(true); 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); 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) 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_export(true) |
sidebar->show_send(send_gcode_shown) | sidebar->show_send(send_gcode_shown) |
sidebar->show_export_removable(removable_media_status.has_removable_drives) | 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(); sidebar->Layout();
} }
else 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_export(!ready_to_slice) |
sidebar->show_send(send_gcode_shown && !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_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(); sidebar->Layout();
} }
} }
@ -4881,6 +4990,9 @@ void Plater::export_gcode(bool prefer_removable)
if (p->model.objects.empty()) if (p->model.objects.empty())
return; return;
if (p->process_completed_with_error)//here
return;
// If possible, remove accents from accented latin characters. // If possible, remove accents from accented latin characters.
// This function is useful for generating file names to be processed by legacy firmwares. // This function is useful for generating file names to be processed by legacy firmwares.
fs::path default_output_file; 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()); p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str());
} }
void Plater::reslice() void Plater::reslice()
{ {
// Stop arrange and (or) optimize rotation tasks. // Stop arrange and (or) optimize rotation tasks.
@ -5885,6 +5996,16 @@ Mouse3DController& Plater::get_mouse3d_controller()
return p->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() const { return p->can_delete(); }
bool Plater::can_delete_all() const { return p->can_delete_all(); } bool Plater::can_delete_all() const { return p->can_delete_all(); }
bool Plater::can_increase_instances() const { return p->can_increase_instances(); } bool Plater::can_increase_instances() const { return p->can_increase_instances(); }

View File

@ -47,6 +47,7 @@ class ObjectLayers;
class ObjectList; class ObjectList;
class GLCanvas3D; class GLCanvas3D;
class Mouse3DController; class Mouse3DController;
class NotificationManager;
struct Camera; struct Camera;
class Bed3D; class Bed3D;
class GLToolbar; class GLToolbar;
@ -130,8 +131,9 @@ public:
bool show_reslice(bool show) const; bool show_reslice(bool show) const;
bool show_export(bool show) const; bool show_export(bool show) const;
bool show_send(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 show_export_removable(bool show) const;
bool get_eject_shown() const;
bool is_multifilament(); bool is_multifilament();
void update_mode(); void update_mode();
bool is_collapsed(); bool is_collapsed();
@ -362,6 +364,9 @@ public:
#if ENABLE_GCODE_VIEWER #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; 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 #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. // ROII wrapper for suppressing the Undo / Redo snapshot to be taken.
class SuppressSnapshots class SuppressSnapshots

View File

@ -496,6 +496,7 @@ const std::vector<std::string>& Preset::sla_print_options()
"support_head_penetration", "support_head_penetration",
"support_head_width", "support_head_width",
"support_pillar_diameter", "support_pillar_diameter",
"support_small_pillar_diameter_percent",
"support_max_bridges_on_pillar", "support_max_bridges_on_pillar",
"support_pillar_connection_mode", "support_pillar_connection_mode",
"support_buildplate_only", "support_buildplate_only",

View File

@ -3919,6 +3919,7 @@ void TabSLAPrint::build()
optgroup = page->new_optgroup(L("Support pillar")); optgroup = page->new_optgroup(L("Support pillar"));
optgroup->append_single_option_line("support_pillar_diameter"); 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_max_bridges_on_pillar");
optgroup->append_single_option_line("support_pillar_connection_mode"); optgroup->append_single_option_line("support_pillar_connection_mode");

View File

@ -27,6 +27,7 @@
#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/format.hpp" #include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
#include "slic3r/Utils/Http.hpp" #include "slic3r/Utils/Http.hpp"
#include "slic3r/Config/Version.hpp" #include "slic3r/Config/Version.hpp"
#include "slic3r/Config/Snapshot.hpp" #include "slic3r/Config/Snapshot.hpp"
@ -154,6 +155,9 @@ struct PresetUpdater::priv
bool cancel; bool cancel;
std::thread thread; std::thread thread;
bool has_waiting_updates { false };
Updates waiting_updates;
priv(); priv();
void set_download_prefs(AppConfig *app_config); void set_download_prefs(AppConfig *app_config);
@ -165,6 +169,7 @@ struct PresetUpdater::priv
void check_install_indices() const; void check_install_indices() const;
Updates get_config_updates(const Semver& old_slic3r_version) const; Updates get_config_updates(const Semver& old_slic3r_version) const;
void perform_updates(Updates &&updates, bool snapshot = true) const; void perform_updates(Updates &&updates, bool snapshot = true) const;
void set_waiting_updates(Updates u);
}; };
PresetUpdater::priv::priv() PresetUpdater::priv::priv()
@ -326,7 +331,15 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors)
continue; continue;
} }
Slic3r::rename_file(idx_path_temp, idx_path); 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) if (cancel)
return; 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() : PresetUpdater::PresetUpdater() :
p(new priv()) 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); auto updates = p->get_config_updates(old_slic3r_version);
if (updates.incompats.size() > 0) { if (updates.incompats.size() > 0) {
@ -779,30 +798,38 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver &old_slic3
} }
// regular update // 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; std::vector<GUI::MsgUpdateConfig::Update> updates_msg;
for (const auto &update : updates.updates) { for (const auto& update : updates.updates) {
std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string(); 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)); 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(); const auto res = dlg.ShowModal();
if (res == wxID_OK) { if (res == wxID_OK) {
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
p->perform_updates(std::move(updates)); p->perform_updates(std::move(updates));
// Reload global configuration // Reload global configuration
auto *app_config = GUI::wxGetApp().app_config; auto* app_config = GUI::wxGetApp().app_config;
GUI::wxGetApp().preset_bundle->load_presets(*app_config); GUI::wxGetApp().preset_bundle->load_presets(*app_config);
GUI::wxGetApp().load_current_presets(); GUI::wxGetApp().load_current_presets();
return R_UPDATE_INSTALLED; return R_UPDATE_INSTALLED;
}
else {
BOOST_LOG_TRIVIAL(info) << "User refused the update";
return R_UPDATE_REJECT;
}
} else { } else {
BOOST_LOG_TRIVIAL(info) << "User refused the update"; p->set_waiting_updates(updates);
return R_UPDATE_REJECT; 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 { } else {
BOOST_LOG_TRIVIAL(info) << "No configuration updates available."; 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); 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;
}
}
} }

View File

@ -35,16 +35,20 @@ public:
R_INCOMPAT_CONFIGURED, R_INCOMPAT_CONFIGURED,
R_UPDATE_INSTALLED, R_UPDATE_INSTALLED,
R_UPDATE_REJECT, R_UPDATE_REJECT,
R_UPDATE_NOTIFICATION
}; };
// If updating is enabled, check if updates are available in cache, if so, ask about installation. // 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. // 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 // 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. // 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). // "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 install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
void on_update_notification_confirm();
private: private:
struct priv; struct priv;
std::unique_ptr<priv> p; std::unique_ptr<priv> p;

View File

@ -1,7 +1,7 @@
get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp
sla_print_tests.cpp sla_print_tests.cpp
sla_test_utils.hpp sla_test_utils.cpp sla_test_utils.hpp sla_test_utils.cpp sla_treebuilder_tests.cpp
sla_raycast_tests.cpp) sla_raycast_tests.cpp)
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r) target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests") set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")

View File

@ -4,6 +4,8 @@
#include "sla_test_utils.hpp" #include "sla_test_utils.hpp"
#include <libslic3r/SLA/SupportTreeMesher.hpp>
namespace { namespace {
const char *const BELOW_PAD_TEST_OBJECTS[] = { const char *const BELOW_PAD_TEST_OBJECTS[] = {
@ -37,9 +39,9 @@ TEST_CASE("Support point generator should be deterministic if seeded",
"[SLASupportGeneration], [SLAPointGen]") { "[SLASupportGeneration], [SLAPointGen]") {
TriangleMesh mesh = load_model("A_upsidedown.obj"); TriangleMesh mesh = load_model("A_upsidedown.obj");
sla::EigenMesh3D emesh{mesh}; sla::IndexedMesh emesh{mesh};
sla::SupportConfig supportcfg; sla::SupportTreeConfig supportcfg;
sla::SupportPointGenerator::Config autogencfg; sla::SupportPointGenerator::Config autogencfg;
autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm); autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm);
sla::SupportPointGenerator point_gen{emesh, autogencfg, [] {}, [](int) {}}; sla::SupportPointGenerator point_gen{emesh, autogencfg, [] {}, [](int) {}};
@ -124,14 +126,14 @@ TEST_CASE("WingedPadAroundObjectIsValid", "[SLASupportGeneration]") {
} }
TEST_CASE("ElevatedSupportGeometryIsValid", "[SLASupportGeneration]") { TEST_CASE("ElevatedSupportGeometryIsValid", "[SLASupportGeneration]") {
sla::SupportConfig supportcfg; sla::SupportTreeConfig supportcfg;
supportcfg.object_elevation_mm = 5.; supportcfg.object_elevation_mm = 5.;
for (auto fname : SUPPORT_TEST_MODELS) test_supports(fname); for (auto fname : SUPPORT_TEST_MODELS) test_supports(fname);
} }
TEST_CASE("FloorSupportGeometryIsValid", "[SLASupportGeneration]") { TEST_CASE("FloorSupportGeometryIsValid", "[SLASupportGeneration]") {
sla::SupportConfig supportcfg; sla::SupportTreeConfig supportcfg;
supportcfg.object_elevation_mm = 0; supportcfg.object_elevation_mm = 0;
for (auto &fname: SUPPORT_TEST_MODELS) test_supports(fname, supportcfg); for (auto &fname: SUPPORT_TEST_MODELS) test_supports(fname, supportcfg);
@ -139,7 +141,7 @@ TEST_CASE("FloorSupportGeometryIsValid", "[SLASupportGeneration]") {
TEST_CASE("ElevatedSupportsDoNotPierceModel", "[SLASupportGeneration]") { TEST_CASE("ElevatedSupportsDoNotPierceModel", "[SLASupportGeneration]") {
sla::SupportConfig supportcfg; sla::SupportTreeConfig supportcfg;
for (auto fname : SUPPORT_TEST_MODELS) for (auto fname : SUPPORT_TEST_MODELS)
test_support_model_collision(fname, supportcfg); test_support_model_collision(fname, supportcfg);
@ -147,7 +149,7 @@ TEST_CASE("ElevatedSupportsDoNotPierceModel", "[SLASupportGeneration]") {
TEST_CASE("FloorSupportsDoNotPierceModel", "[SLASupportGeneration]") { TEST_CASE("FloorSupportsDoNotPierceModel", "[SLASupportGeneration]") {
sla::SupportConfig supportcfg; sla::SupportTreeConfig supportcfg;
supportcfg.object_elevation_mm = 0; supportcfg.object_elevation_mm = 0;
for (auto fname : SUPPORT_TEST_MODELS) for (auto fname : SUPPORT_TEST_MODELS)
@ -228,3 +230,12 @@ TEST_CASE("Triangle mesh conversions should be correct", "[SLAConversions]")
cntr.from_obj(infile); cntr.from_obj(infile);
} }
} }
TEST_CASE("halfcone test", "[halfcone]") {
sla::DiffBridge br{Vec3d{1., 1., 1.}, Vec3d{10., 10., 10.}, 0.25, 0.5};
TriangleMesh m = sla::to_triangle_mesh(sla::get_mesh(br, 45));
m.require_shared_vertices();
m.WriteOBJFile("Halfcone.obj");
}

View File

@ -1,7 +1,7 @@
#include <catch2/catch.hpp> #include <catch2/catch.hpp>
#include <test_utils.hpp> #include <test_utils.hpp>
#include <libslic3r/SLA/EigenMesh3D.hpp> #include <libslic3r/SLA/IndexedMesh.hpp>
#include <libslic3r/SLA/Hollowing.hpp> #include <libslic3r/SLA/Hollowing.hpp>
#include "sla_test_utils.hpp" #include "sla_test_utils.hpp"
@ -65,7 +65,7 @@ TEST_CASE("Raycaster with loaded drillholes", "[sla_raycast]")
cube.merge(*cube_inside); cube.merge(*cube_inside);
cube.require_shared_vertices(); cube.require_shared_vertices();
sla::EigenMesh3D emesh{cube}; sla::IndexedMesh emesh{cube};
emesh.load_holes(holes); emesh.load_holes(holes);
Vec3d s = center.cast<double>(); Vec3d s = center.cast<double>();

View File

@ -2,13 +2,13 @@
#include "libslic3r/SLA/AGGRaster.hpp" #include "libslic3r/SLA/AGGRaster.hpp"
void test_support_model_collision(const std::string &obj_filename, void test_support_model_collision(const std::string &obj_filename,
const sla::SupportConfig &input_supportcfg, const sla::SupportTreeConfig &input_supportcfg,
const sla::HollowingConfig &hollowingcfg, const sla::HollowingConfig &hollowingcfg,
const sla::DrainHoles &drainholes) const sla::DrainHoles &drainholes)
{ {
SupportByproducts byproducts; SupportByproducts byproducts;
sla::SupportConfig supportcfg = input_supportcfg; sla::SupportTreeConfig supportcfg = input_supportcfg;
// Set head penetration to a small negative value which should ensure that // Set head penetration to a small negative value which should ensure that
// the supports will not touch the model body. // the supports will not touch the model body.
@ -69,11 +69,12 @@ void export_failed_case(const std::vector<ExPolygons> &support_slices, const Sup
m.merge(byproducts.input_mesh); m.merge(byproducts.input_mesh);
m.repair(); m.repair();
m.require_shared_vertices(); m.require_shared_vertices();
m.WriteOBJFile(byproducts.obj_fname.c_str()); m.WriteOBJFile((Catch::getResultCapture().getCurrentTestName() + "_" +
byproducts.obj_fname).c_str());
} }
void test_supports(const std::string &obj_filename, void test_supports(const std::string &obj_filename,
const sla::SupportConfig &supportcfg, const sla::SupportTreeConfig &supportcfg,
const sla::HollowingConfig &hollowingcfg, const sla::HollowingConfig &hollowingcfg,
const sla::DrainHoles &drainholes, const sla::DrainHoles &drainholes,
SupportByproducts &out) SupportByproducts &out)
@ -104,7 +105,7 @@ void test_supports(const std::string &obj_filename,
// Create the special index-triangle mesh with spatial indexing which // Create the special index-triangle mesh with spatial indexing which
// is the input of the support point and support mesh generators // is the input of the support point and support mesh generators
sla::EigenMesh3D emesh{mesh}; sla::IndexedMesh emesh{mesh};
#ifdef SLIC3R_HOLE_RAYCASTER #ifdef SLIC3R_HOLE_RAYCASTER
if (hollowingcfg.enabled) if (hollowingcfg.enabled)
@ -129,8 +130,7 @@ void test_supports(const std::string &obj_filename,
// If there is no elevation, support points shall be removed from the // If there is no elevation, support points shall be removed from the
// bottom of the object. // bottom of the object.
if (std::abs(supportcfg.object_elevation_mm) < EPSILON) { if (std::abs(supportcfg.object_elevation_mm) < EPSILON) {
sla::remove_bottom_points(support_points, zmin, sla::remove_bottom_points(support_points, zmin + supportcfg.base_height_mm);
supportcfg.base_height_mm);
} else { } else {
// Should be support points at least on the bottom of the model // Should be support points at least on the bottom of the model
REQUIRE_FALSE(support_points.empty()); REQUIRE_FALSE(support_points.empty());
@ -141,7 +141,8 @@ void test_supports(const std::string &obj_filename,
// Generate the actual support tree // Generate the actual support tree
sla::SupportTreeBuilder treebuilder; sla::SupportTreeBuilder treebuilder;
treebuilder.build(sla::SupportableMesh{emesh, support_points, supportcfg}); sla::SupportableMesh sm{emesh, support_points, supportcfg};
sla::SupportTreeBuildsteps::execute(treebuilder, sm);
check_support_tree_integrity(treebuilder, supportcfg); check_support_tree_integrity(treebuilder, supportcfg);
@ -157,8 +158,8 @@ void test_supports(const std::string &obj_filename,
if (std::abs(supportcfg.object_elevation_mm) < EPSILON) if (std::abs(supportcfg.object_elevation_mm) < EPSILON)
allowed_zmin = zmin - 2 * supportcfg.head_back_radius_mm; allowed_zmin = zmin - 2 * supportcfg.head_back_radius_mm;
REQUIRE(obb.min.z() >= allowed_zmin); REQUIRE(obb.min.z() >= Approx(allowed_zmin));
REQUIRE(obb.max.z() <= zmax); REQUIRE(obb.max.z() <= Approx(zmax));
// Move out the support tree into the byproducts, we can examine it further // Move out the support tree into the byproducts, we can examine it further
// in various tests. // in various tests.
@ -168,15 +169,15 @@ void test_supports(const std::string &obj_filename,
} }
void check_support_tree_integrity(const sla::SupportTreeBuilder &stree, void check_support_tree_integrity(const sla::SupportTreeBuilder &stree,
const sla::SupportConfig &cfg) const sla::SupportTreeConfig &cfg)
{ {
double gnd = stree.ground_level; double gnd = stree.ground_level;
double H1 = cfg.max_solo_pillar_height_mm; double H1 = cfg.max_solo_pillar_height_mm;
double H2 = cfg.max_dual_pillar_height_mm; double H2 = cfg.max_dual_pillar_height_mm;
for (const sla::Head &head : stree.heads()) { for (const sla::Head &head : stree.heads()) {
REQUIRE((!head.is_valid() || head.pillar_id != sla::ID_UNSET || REQUIRE((!head.is_valid() || head.pillar_id != sla::SupportTreeNode::ID_UNSET ||
head.bridge_id != sla::ID_UNSET)); head.bridge_id != sla::SupportTreeNode::ID_UNSET));
} }
for (const sla::Pillar &pillar : stree.pillars()) { for (const sla::Pillar &pillar : stree.pillars()) {

View File

@ -67,16 +67,16 @@ struct SupportByproducts
const constexpr float CLOSING_RADIUS = 0.005f; const constexpr float CLOSING_RADIUS = 0.005f;
void check_support_tree_integrity(const sla::SupportTreeBuilder &stree, void check_support_tree_integrity(const sla::SupportTreeBuilder &stree,
const sla::SupportConfig &cfg); const sla::SupportTreeConfig &cfg);
void test_supports(const std::string &obj_filename, void test_supports(const std::string &obj_filename,
const sla::SupportConfig &supportcfg, const sla::SupportTreeConfig &supportcfg,
const sla::HollowingConfig &hollowingcfg, const sla::HollowingConfig &hollowingcfg,
const sla::DrainHoles &drainholes, const sla::DrainHoles &drainholes,
SupportByproducts &out); SupportByproducts &out);
inline void test_supports(const std::string &obj_filename, inline void test_supports(const std::string &obj_filename,
const sla::SupportConfig &supportcfg, const sla::SupportTreeConfig &supportcfg,
SupportByproducts &out) SupportByproducts &out)
{ {
sla::HollowingConfig hcfg; sla::HollowingConfig hcfg;
@ -85,7 +85,7 @@ inline void test_supports(const std::string &obj_filename,
} }
inline void test_supports(const std::string &obj_filename, inline void test_supports(const std::string &obj_filename,
const sla::SupportConfig &supportcfg = {}) const sla::SupportTreeConfig &supportcfg = {})
{ {
SupportByproducts byproducts; SupportByproducts byproducts;
test_supports(obj_filename, supportcfg, byproducts); test_supports(obj_filename, supportcfg, byproducts);
@ -97,13 +97,13 @@ void export_failed_case(const std::vector<ExPolygons> &support_slices,
void test_support_model_collision( void test_support_model_collision(
const std::string &obj_filename, const std::string &obj_filename,
const sla::SupportConfig &input_supportcfg, const sla::SupportTreeConfig &input_supportcfg,
const sla::HollowingConfig &hollowingcfg, const sla::HollowingConfig &hollowingcfg,
const sla::DrainHoles &drainholes); const sla::DrainHoles &drainholes);
inline void test_support_model_collision( inline void test_support_model_collision(
const std::string &obj_filename, const std::string &obj_filename,
const sla::SupportConfig &input_supportcfg = {}) const sla::SupportTreeConfig &input_supportcfg = {})
{ {
sla::HollowingConfig hcfg; sla::HollowingConfig hcfg;
hcfg.enabled = false; hcfg.enabled = false;

View File

@ -0,0 +1,99 @@
//#include <catch2/catch.hpp>
//#include <test_utils.hpp>
//#include "libslic3r/TriangleMesh.hpp"
//#include "libslic3r/SLA/SupportTreeBuildsteps.hpp"
//#include "libslic3r/SLA/SupportTreeMesher.hpp"
//TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]") {
// using namespace Slic3r;
// TriangleMesh cube = make_cube(10., 10., 10.);
// sla::SupportConfig cfg = {}; // use default config
// sla::SupportPoints pts = {{10.f, 5.f, 5.f, float(cfg.head_front_radius_mm), false}};
// sla::SupportableMesh sm{cube, pts, cfg};
// size_t steps = 45;
// SECTION("Bridge is straight horizontal and pointing away from the cube") {
// sla::Bridge bridge(pts[0].pos.cast<double>(), Vec3d{15., 5., 5.},
// pts[0].head_front_radius);
// auto hit = sla::query_hit(sm, bridge);
// REQUIRE(std::isinf(hit.distance()));
// cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps)));
// cube.require_shared_vertices();
// cube.WriteOBJFile("cube1.obj");
// }
// SECTION("Bridge is tilted down in 45 degrees, pointing away from the cube") {
// sla::Bridge bridge(pts[0].pos.cast<double>(), Vec3d{15., 5., 0.},
// pts[0].head_front_radius);
// auto hit = sla::query_hit(sm, bridge);
// REQUIRE(std::isinf(hit.distance()));
// cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps)));
// cube.require_shared_vertices();
// cube.WriteOBJFile("cube2.obj");
// }
//}
//TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") {
// using namespace Slic3r;
// TriangleMesh sphere = make_sphere(1.);
// sla::SupportConfig cfg = {}; // use default config
// cfg.head_back_radius_mm = cfg.head_front_radius_mm;
// sla::SupportPoints pts = {{1.f, 0.f, 0.f, float(cfg.head_front_radius_mm), false}};
// sla::SupportableMesh sm{sphere, pts, cfg};
// size_t steps = 45;
// SECTION("Bridge is straight horizontal and pointing away from the sphere") {
// sla::Bridge bridge(pts[0].pos.cast<double>(), Vec3d{2., 0., 0.},
// pts[0].head_front_radius);
// auto hit = sla::query_hit(sm, bridge);
// sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps)));
// sphere.require_shared_vertices();
// sphere.WriteOBJFile("sphere1.obj");
// REQUIRE(std::isinf(hit.distance()));
// }
// SECTION("Bridge is tilted down 45 deg and pointing away from the sphere") {
// sla::Bridge bridge(pts[0].pos.cast<double>(), Vec3d{2., 0., -2.},
// pts[0].head_front_radius);
// auto hit = sla::query_hit(sm, bridge);
// sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps)));
// sphere.require_shared_vertices();
// sphere.WriteOBJFile("sphere2.obj");
// REQUIRE(std::isinf(hit.distance()));
// }
// SECTION("Bridge is tilted down 90 deg and pointing away from the sphere") {
// sla::Bridge bridge(pts[0].pos.cast<double>(), Vec3d{1., 0., -2.},
// pts[0].head_front_radius);
// auto hit = sla::query_hit(sm, bridge);
// sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps)));
// sphere.require_shared_vertices();
// sphere.WriteOBJFile("sphere3.obj");
// REQUIRE(std::isinf(hit.distance()));
// }
//}