Fixed conflicts after merge with master
This commit is contained in:
commit
25c3552555
10
resources/icons/cancel.svg
Normal file
10
resources/icons/cancel.svg
Normal 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 |
81
resources/icons/cross_focus_large.svg
Normal file
81
resources/icons/cross_focus_large.svg
Normal 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 |
72
resources/icons/timer_dot.svg
Normal file
72
resources/icons/timer_dot.svg
Normal 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 |
73
resources/icons/timer_dot_empty.svg
Normal file
73
resources/icons/timer_dot_empty.svg
Normal 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 |
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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
380
src/libslic3r/Optimizer.hpp
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
#ifndef NLOPTOPTIMIZER_HPP
|
||||||
|
#define NLOPTOPTIMIZER_HPP
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(push)
|
||||||
|
#pragma warning(disable: 4244)
|
||||||
|
#pragma warning(disable: 4267)
|
||||||
|
#endif
|
||||||
|
#include <nlopt.h>
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(pop)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include <tuple>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <functional>
|
||||||
|
#include <limits>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace Slic3r { namespace opt {
|
||||||
|
|
||||||
|
// A type to hold the complete result of the optimization.
|
||||||
|
template<size_t N> struct Result {
|
||||||
|
int resultcode;
|
||||||
|
std::array<double, N> optimum;
|
||||||
|
double score;
|
||||||
|
};
|
||||||
|
|
||||||
|
// An interval of possible input values for optimization
|
||||||
|
class Bound {
|
||||||
|
double m_min, m_max;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Bound(double min = std::numeric_limits<double>::min(),
|
||||||
|
double max = std::numeric_limits<double>::max())
|
||||||
|
: m_min(min), m_max(max)
|
||||||
|
{}
|
||||||
|
|
||||||
|
double min() const noexcept { return m_min; }
|
||||||
|
double max() const noexcept { return m_max; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper types for optimization function input and bounds
|
||||||
|
template<size_t N> using Input = std::array<double, N>;
|
||||||
|
template<size_t N> using Bounds = std::array<Bound, N>;
|
||||||
|
|
||||||
|
// A type for specifying the stop criteria. Setter methods can be concatenated
|
||||||
|
class StopCriteria {
|
||||||
|
|
||||||
|
// If the absolute value difference between two scores.
|
||||||
|
double m_abs_score_diff = std::nan("");
|
||||||
|
|
||||||
|
// If the relative value difference between two scores.
|
||||||
|
double m_rel_score_diff = std::nan("");
|
||||||
|
|
||||||
|
// Stop if this value or better is found.
|
||||||
|
double m_stop_score = std::nan("");
|
||||||
|
|
||||||
|
// A predicate that if evaluates to true, the optimization should terminate
|
||||||
|
// and the best result found prior to termination should be returned.
|
||||||
|
std::function<bool()> m_stop_condition = [] { return false; };
|
||||||
|
|
||||||
|
// The max allowed number of iterations.
|
||||||
|
unsigned m_max_iterations = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
StopCriteria & abs_score_diff(double val)
|
||||||
|
{
|
||||||
|
m_abs_score_diff = val; return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
double abs_score_diff() const { return m_abs_score_diff; }
|
||||||
|
|
||||||
|
StopCriteria & rel_score_diff(double val)
|
||||||
|
{
|
||||||
|
m_rel_score_diff = val; return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
double rel_score_diff() const { return m_rel_score_diff; }
|
||||||
|
|
||||||
|
StopCriteria & stop_score(double val)
|
||||||
|
{
|
||||||
|
m_stop_score = val; return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
double stop_score() const { return m_stop_score; }
|
||||||
|
|
||||||
|
StopCriteria & max_iterations(double val)
|
||||||
|
{
|
||||||
|
m_max_iterations = val; return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
double max_iterations() const { return m_max_iterations; }
|
||||||
|
|
||||||
|
template<class Fn> StopCriteria & stop_condition(Fn &&cond)
|
||||||
|
{
|
||||||
|
m_stop_condition = cond; return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool stop_condition() { return m_stop_condition(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper class to use optimization methods involving gradient.
|
||||||
|
template<size_t N> struct ScoreGradient {
|
||||||
|
double score;
|
||||||
|
std::optional<std::array<double, N>> gradient;
|
||||||
|
|
||||||
|
ScoreGradient(double s, const std::array<double, N> &grad)
|
||||||
|
: score{s}, gradient{grad}
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper to be used in static_assert.
|
||||||
|
template<class T> struct always_false { enum { value = false }; };
|
||||||
|
|
||||||
|
// Basic interface to optimizer object
|
||||||
|
template<class Method, class Enable = void> class Optimizer {
|
||||||
|
public:
|
||||||
|
|
||||||
|
Optimizer(const StopCriteria &)
|
||||||
|
{
|
||||||
|
static_assert (always_false<Method>::value,
|
||||||
|
"Optimizer unimplemented for given method!");
|
||||||
|
}
|
||||||
|
|
||||||
|
Optimizer<Method> &to_min() { return *this; }
|
||||||
|
Optimizer<Method> &to_max() { return *this; }
|
||||||
|
Optimizer<Method> &set_criteria(const StopCriteria &) { return *this; }
|
||||||
|
StopCriteria get_criteria() const { return {}; };
|
||||||
|
|
||||||
|
template<class Func, size_t N>
|
||||||
|
Result<N> optimize(Func&& func,
|
||||||
|
const Input<N> &initvals,
|
||||||
|
const Bounds<N>& bounds) { return {}; }
|
||||||
|
|
||||||
|
// optional for randomized methods:
|
||||||
|
void seed(long /*s*/) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
// Helper types for NLopt algorithm selection in template contexts
|
||||||
|
template<nlopt_algorithm alg> struct NLoptAlg {};
|
||||||
|
|
||||||
|
// NLopt can combine multiple algorithms if one is global an other is a local
|
||||||
|
// method. This is how template specializations can be informed about this fact.
|
||||||
|
template<nlopt_algorithm gl_alg, nlopt_algorithm lc_alg = NLOPT_LN_NELDERMEAD>
|
||||||
|
struct NLoptAlgComb {};
|
||||||
|
|
||||||
|
template<class M> struct IsNLoptAlg {
|
||||||
|
static const constexpr bool value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<nlopt_algorithm a> struct IsNLoptAlg<NLoptAlg<a>> {
|
||||||
|
static const constexpr bool value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<nlopt_algorithm a1, nlopt_algorithm a2>
|
||||||
|
struct IsNLoptAlg<NLoptAlgComb<a1, a2>> {
|
||||||
|
static const constexpr bool value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class M, class T = void>
|
||||||
|
using NLoptOnly = std::enable_if_t<IsNLoptAlg<M>::value, T>;
|
||||||
|
|
||||||
|
// Helper to convert C style array to std::array. The copy should be optimized
|
||||||
|
// away with modern compilers.
|
||||||
|
template<size_t N, class T> auto to_arr(const T *a)
|
||||||
|
{
|
||||||
|
std::array<T, N> r;
|
||||||
|
std::copy(a, a + N, std::begin(r));
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<size_t N, class T> auto to_arr(const T (&a) [N])
|
||||||
|
{
|
||||||
|
return to_arr<N>(static_cast<const T *>(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class OptDir { MIN, MAX }; // Where to optimize
|
||||||
|
|
||||||
|
struct NLopt { // Helper RAII class for nlopt_opt
|
||||||
|
nlopt_opt ptr = nullptr;
|
||||||
|
|
||||||
|
template<class...A> explicit NLopt(A&&...a)
|
||||||
|
{
|
||||||
|
ptr = nlopt_create(std::forward<A>(a)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
NLopt(const NLopt&) = delete;
|
||||||
|
NLopt(NLopt&&) = delete;
|
||||||
|
NLopt& operator=(const NLopt&) = delete;
|
||||||
|
NLopt& operator=(NLopt&&) = delete;
|
||||||
|
|
||||||
|
~NLopt() { nlopt_destroy(ptr); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Method> class NLoptOpt {};
|
||||||
|
|
||||||
|
// Optimizers based on NLopt.
|
||||||
|
template<nlopt_algorithm alg> class NLoptOpt<NLoptAlg<alg>> {
|
||||||
|
protected:
|
||||||
|
StopCriteria m_stopcr;
|
||||||
|
OptDir m_dir;
|
||||||
|
|
||||||
|
template<class Fn> using TOptData =
|
||||||
|
std::tuple<std::remove_reference_t<Fn>*, NLoptOpt*, nlopt_opt>;
|
||||||
|
|
||||||
|
template<class Fn, size_t N>
|
||||||
|
static double optfunc(unsigned n, const double *params,
|
||||||
|
double *gradient,
|
||||||
|
void *data)
|
||||||
|
{
|
||||||
|
assert(n >= N);
|
||||||
|
|
||||||
|
auto tdata = static_cast<TOptData<Fn>*>(data);
|
||||||
|
|
||||||
|
if (std::get<1>(*tdata)->m_stopcr.stop_condition())
|
||||||
|
nlopt_force_stop(std::get<2>(*tdata));
|
||||||
|
|
||||||
|
auto fnptr = std::get<0>(*tdata);
|
||||||
|
auto funval = to_arr<N>(params);
|
||||||
|
|
||||||
|
double scoreval = 0.;
|
||||||
|
using RetT = decltype((*fnptr)(funval));
|
||||||
|
if constexpr (std::is_convertible_v<RetT, ScoreGradient<N>>) {
|
||||||
|
ScoreGradient<N> score = (*fnptr)(funval);
|
||||||
|
for (size_t i = 0; i < n; ++i) gradient[i] = (*score.gradient)[i];
|
||||||
|
scoreval = score.score;
|
||||||
|
} else {
|
||||||
|
scoreval = (*fnptr)(funval);
|
||||||
|
}
|
||||||
|
|
||||||
|
return scoreval;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<size_t N>
|
||||||
|
void set_up(NLopt &nl, const Bounds<N>& bounds)
|
||||||
|
{
|
||||||
|
std::array<double, N> lb, ub;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < N; ++i) {
|
||||||
|
lb[i] = bounds[i].min();
|
||||||
|
ub[i] = bounds[i].max();
|
||||||
|
}
|
||||||
|
|
||||||
|
nlopt_set_lower_bounds(nl.ptr, lb.data());
|
||||||
|
nlopt_set_upper_bounds(nl.ptr, ub.data());
|
||||||
|
|
||||||
|
double abs_diff = m_stopcr.abs_score_diff();
|
||||||
|
double rel_diff = m_stopcr.rel_score_diff();
|
||||||
|
double stopval = m_stopcr.stop_score();
|
||||||
|
if(!std::isnan(abs_diff)) nlopt_set_ftol_abs(nl.ptr, abs_diff);
|
||||||
|
if(!std::isnan(rel_diff)) nlopt_set_ftol_rel(nl.ptr, rel_diff);
|
||||||
|
if(!std::isnan(stopval)) nlopt_set_stopval(nl.ptr, stopval);
|
||||||
|
|
||||||
|
if(this->m_stopcr.max_iterations() > 0)
|
||||||
|
nlopt_set_maxeval(nl.ptr, this->m_stopcr.max_iterations());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Fn, size_t N>
|
||||||
|
Result<N> optimize(NLopt &nl, Fn &&fn, const Input<N> &initvals)
|
||||||
|
{
|
||||||
|
Result<N> r;
|
||||||
|
|
||||||
|
TOptData<Fn> data = std::make_tuple(&fn, this, nl.ptr);
|
||||||
|
|
||||||
|
switch(m_dir) {
|
||||||
|
case OptDir::MIN:
|
||||||
|
nlopt_set_min_objective(nl.ptr, optfunc<Fn, N>, &data); break;
|
||||||
|
case OptDir::MAX:
|
||||||
|
nlopt_set_max_objective(nl.ptr, optfunc<Fn, N>, &data); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
r.optimum = initvals;
|
||||||
|
r.resultcode = nlopt_optimize(nl.ptr, r.optimum.data(), &r.score);
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
template<class Func, size_t N>
|
||||||
|
Result<N> optimize(Func&& func,
|
||||||
|
const Input<N> &initvals,
|
||||||
|
const Bounds<N>& bounds)
|
||||||
|
{
|
||||||
|
NLopt nl{alg, N};
|
||||||
|
set_up(nl, bounds);
|
||||||
|
|
||||||
|
return optimize(nl, std::forward<Func>(func), initvals);
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit NLoptOpt(StopCriteria stopcr = {}) : m_stopcr(stopcr) {}
|
||||||
|
|
||||||
|
void set_criteria(const StopCriteria &cr) { m_stopcr = cr; }
|
||||||
|
const StopCriteria &get_criteria() const noexcept { return m_stopcr; }
|
||||||
|
void set_dir(OptDir dir) noexcept { m_dir = dir; }
|
||||||
|
|
||||||
|
void seed(long s) { nlopt_srand(s); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<nlopt_algorithm glob, nlopt_algorithm loc>
|
||||||
|
class NLoptOpt<NLoptAlgComb<glob, loc>>: public NLoptOpt<NLoptAlg<glob>>
|
||||||
|
{
|
||||||
|
using Base = NLoptOpt<NLoptAlg<glob>>;
|
||||||
|
public:
|
||||||
|
|
||||||
|
template<class Fn, size_t N>
|
||||||
|
Result<N> optimize(Fn&& f,
|
||||||
|
const Input<N> &initvals,
|
||||||
|
const Bounds<N>& bounds)
|
||||||
|
{
|
||||||
|
NLopt nl_glob{glob, N}, nl_loc{loc, N};
|
||||||
|
|
||||||
|
Base::set_up(nl_glob, bounds);
|
||||||
|
Base::set_up(nl_loc, bounds);
|
||||||
|
nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr);
|
||||||
|
|
||||||
|
return Base::optimize(nl_glob, std::forward<Fn>(f), initvals);
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit NLoptOpt(StopCriteria stopcr = {}) : Base{stopcr} {}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace detail;
|
||||||
|
|
||||||
|
// Optimizers based on NLopt.
|
||||||
|
template<class M> class Optimizer<M, detail::NLoptOnly<M>> {
|
||||||
|
detail::NLoptOpt<M> m_opt;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Optimizer& to_max() { m_opt.set_dir(detail::OptDir::MAX); return *this; }
|
||||||
|
Optimizer& to_min() { m_opt.set_dir(detail::OptDir::MIN); return *this; }
|
||||||
|
|
||||||
|
template<class Func, size_t N>
|
||||||
|
Result<N> optimize(Func&& func,
|
||||||
|
const Input<N> &initvals,
|
||||||
|
const Bounds<N>& bounds)
|
||||||
|
{
|
||||||
|
return m_opt.optimize(std::forward<Func>(func), initvals, bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit Optimizer(StopCriteria stopcr = {}) : m_opt(stopcr) {}
|
||||||
|
|
||||||
|
Optimizer &set_criteria(const StopCriteria &cr)
|
||||||
|
{
|
||||||
|
m_opt.set_criteria(cr); return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StopCriteria &get_criteria() const { return m_opt.get_criteria(); }
|
||||||
|
|
||||||
|
void seed(long s) { m_opt.seed(s); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<size_t N> Bounds<N> bounds(const Bound (&b) [N]) { return detail::to_arr(b); }
|
||||||
|
template<size_t N> Input<N> initvals(const double (&a) [N]) { return detail::to_arr(a); }
|
||||||
|
template<size_t N> auto score_gradient(double s, const double (&grad)[N])
|
||||||
|
{
|
||||||
|
return ScoreGradient<N>(s, detail::to_arr(grad));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Predefinded NLopt algorithms that are used in the codebase
|
||||||
|
using AlgNLoptGenetic = detail::NLoptAlgComb<NLOPT_GN_ESCH>;
|
||||||
|
using AlgNLoptSubplex = detail::NLoptAlg<NLOPT_LN_SBPLX>;
|
||||||
|
using AlgNLoptSimplex = detail::NLoptAlg<NLOPT_LN_NELDERMEAD>;
|
||||||
|
|
||||||
|
// TODO: define others if needed...
|
||||||
|
|
||||||
|
// Helper defs for pre-crafted global and local optimizers that work well.
|
||||||
|
using DefaultGlobalOptimizer = Optimizer<AlgNLoptGenetic>;
|
||||||
|
using DefaultLocalOptimizer = Optimizer<AlgNLoptSubplex>;
|
||||||
|
|
||||||
|
}} // namespace Slic3r::opt
|
||||||
|
|
||||||
|
#endif // NLOPTOPTIMIZER_HPP
|
@ -60,10 +60,13 @@ inline int64_t cross2(const Vec2i64 &v1, const Vec2i64 &v2) { return v1(0) * v2(
|
|||||||
inline float cross2(const Vec2f &v1, const Vec2f &v2) { return v1(0) * v2(1) - v1(1) * v2(0); }
|
inline 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); }
|
||||||
|
@ -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"
|
||||||
|
@ -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);
|
||||||
|
@ -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 {
|
||||||
|
152
src/libslic3r/SLA/Clustering.cpp
Normal file
152
src/libslic3r/SLA/Clustering.cpp
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
#include "Clustering.hpp"
|
||||||
|
#include "boost/geometry/index/rtree.hpp"
|
||||||
|
|
||||||
|
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||||
|
#include <libslic3r/SLA/BoostAdapter.hpp>
|
||||||
|
|
||||||
|
namespace Slic3r { namespace sla {
|
||||||
|
|
||||||
|
namespace bgi = boost::geometry::index;
|
||||||
|
using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2)
|
||||||
|
{
|
||||||
|
return e1.second < e2.second;
|
||||||
|
};
|
||||||
|
|
||||||
|
ClusteredPoints cluster(Index3D &sindex,
|
||||||
|
unsigned max_points,
|
||||||
|
std::function<std::vector<PointIndexEl>(
|
||||||
|
const Index3D &, const PointIndexEl &)> qfn)
|
||||||
|
{
|
||||||
|
using Elems = std::vector<PointIndexEl>;
|
||||||
|
|
||||||
|
// Recursive function for visiting all the points in a given distance to
|
||||||
|
// each other
|
||||||
|
std::function<void(Elems&, Elems&)> group =
|
||||||
|
[&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster)
|
||||||
|
{
|
||||||
|
for(auto& p : pts) {
|
||||||
|
std::vector<PointIndexEl> tmp = qfn(sindex, p);
|
||||||
|
|
||||||
|
std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements);
|
||||||
|
|
||||||
|
Elems newpts;
|
||||||
|
std::set_difference(tmp.begin(), tmp.end(),
|
||||||
|
cluster.begin(), cluster.end(),
|
||||||
|
std::back_inserter(newpts), cmp_ptidx_elements);
|
||||||
|
|
||||||
|
int c = max_points && newpts.size() + cluster.size() > max_points?
|
||||||
|
int(max_points - cluster.size()) : int(newpts.size());
|
||||||
|
|
||||||
|
cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c);
|
||||||
|
std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements);
|
||||||
|
|
||||||
|
if(!newpts.empty() && (!max_points || cluster.size() < max_points))
|
||||||
|
group(newpts, cluster);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Elems> clusters;
|
||||||
|
for(auto it = sindex.begin(); it != sindex.end();) {
|
||||||
|
Elems cluster = {};
|
||||||
|
Elems pts = {*it};
|
||||||
|
group(pts, cluster);
|
||||||
|
|
||||||
|
for(auto& c : cluster) sindex.remove(c);
|
||||||
|
it = sindex.begin();
|
||||||
|
|
||||||
|
clusters.emplace_back(cluster);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClusteredPoints result;
|
||||||
|
for(auto& cluster : clusters) {
|
||||||
|
result.emplace_back();
|
||||||
|
for(auto c : cluster) result.back().emplace_back(c.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex,
|
||||||
|
const PointIndexEl& p,
|
||||||
|
double dist,
|
||||||
|
unsigned max_points)
|
||||||
|
{
|
||||||
|
std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
|
||||||
|
sindex.query(
|
||||||
|
bgi::nearest(p.first, max_points),
|
||||||
|
std::back_inserter(tmp)
|
||||||
|
);
|
||||||
|
|
||||||
|
for(auto it = tmp.begin(); it < tmp.end(); ++it)
|
||||||
|
if((p.first - it->first).norm() > dist) it = tmp.erase(it);
|
||||||
|
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// Clustering a set of points by the given criteria
|
||||||
|
ClusteredPoints cluster(
|
||||||
|
const std::vector<unsigned>& indices,
|
||||||
|
std::function<Vec3d(unsigned)> pointfn,
|
||||||
|
double dist,
|
||||||
|
unsigned max_points)
|
||||||
|
{
|
||||||
|
// A spatial index for querying the nearest points
|
||||||
|
Index3D sindex;
|
||||||
|
|
||||||
|
// Build the index
|
||||||
|
for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
|
||||||
|
|
||||||
|
return cluster(sindex, max_points,
|
||||||
|
[dist, max_points](const Index3D& sidx, const PointIndexEl& p)
|
||||||
|
{
|
||||||
|
return distance_queryfn(sidx, p, dist, max_points);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clustering a set of points by the given criteria
|
||||||
|
ClusteredPoints cluster(
|
||||||
|
const std::vector<unsigned>& indices,
|
||||||
|
std::function<Vec3d(unsigned)> pointfn,
|
||||||
|
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
|
||||||
|
unsigned max_points)
|
||||||
|
{
|
||||||
|
// A spatial index for querying the nearest points
|
||||||
|
Index3D sindex;
|
||||||
|
|
||||||
|
// Build the index
|
||||||
|
for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
|
||||||
|
|
||||||
|
return cluster(sindex, max_points,
|
||||||
|
[max_points, predicate](const Index3D& sidx, const PointIndexEl& p)
|
||||||
|
{
|
||||||
|
std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
|
||||||
|
sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){
|
||||||
|
return predicate(p, e);
|
||||||
|
}), std::back_inserter(tmp));
|
||||||
|
return tmp;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ClusteredPoints cluster(const Eigen::MatrixXd& pts, double dist, unsigned max_points)
|
||||||
|
{
|
||||||
|
// A spatial index for querying the nearest points
|
||||||
|
Index3D sindex;
|
||||||
|
|
||||||
|
// Build the index
|
||||||
|
for(Eigen::Index i = 0; i < pts.rows(); i++)
|
||||||
|
sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i)));
|
||||||
|
|
||||||
|
return cluster(sindex, max_points,
|
||||||
|
[dist, max_points](const Index3D& sidx, const PointIndexEl& p)
|
||||||
|
{
|
||||||
|
return distance_queryfn(sidx, p, dist, max_points);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}} // namespace Slic3r::sla
|
@ -2,7 +2,8 @@
|
|||||||
#define SLA_CLUSTERING_HPP
|
#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
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
#ifndef SLA_COMMON_HPP
|
|
||||||
#define SLA_COMMON_HPP
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
#include <numeric>
|
|
||||||
#include <functional>
|
|
||||||
#include <Eigen/Geometry>
|
|
||||||
|
|
||||||
|
|
||||||
namespace Slic3r {
|
|
||||||
|
|
||||||
// Typedefs from Point.hpp
|
|
||||||
typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> Vec3f;
|
|
||||||
typedef Eigen::Matrix<double, 3, 1, Eigen::DontAlign> Vec3d;
|
|
||||||
typedef Eigen::Matrix<int, 3, 1, Eigen::DontAlign> Vec3i;
|
|
||||||
typedef Eigen::Matrix<int, 4, 1, Eigen::DontAlign> Vec4i;
|
|
||||||
|
|
||||||
namespace sla {
|
|
||||||
|
|
||||||
using PointSet = Eigen::MatrixXd;
|
|
||||||
|
|
||||||
} // namespace sla
|
|
||||||
} // namespace Slic3r
|
|
||||||
|
|
||||||
|
|
||||||
#endif // SLASUPPORTTREE_HPP
|
|
@ -1,5 +1,5 @@
|
|||||||
#include <libslic3r/SLA/Contour3D.hpp>
|
#include <libslic3r/SLA/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());
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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
|
|
@ -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
|
@ -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 {
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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);
|
||||||
|
@ -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"
|
||||||
|
161
src/libslic3r/SLA/SpatIndex.cpp
Normal file
161
src/libslic3r/SLA/SpatIndex.cpp
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
#include "SpatIndex.hpp"
|
||||||
|
|
||||||
|
// for concave hull merging decisions
|
||||||
|
#include <libslic3r/SLA/BoostAdapter.hpp>
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(push)
|
||||||
|
#pragma warning(disable: 4244)
|
||||||
|
#pragma warning(disable: 4267)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "boost/geometry/index/rtree.hpp"
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(pop)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Slic3r { namespace sla {
|
||||||
|
|
||||||
|
/* **************************************************************************
|
||||||
|
* PointIndex implementation
|
||||||
|
* ************************************************************************** */
|
||||||
|
|
||||||
|
class PointIndex::Impl {
|
||||||
|
public:
|
||||||
|
using BoostIndex = boost::geometry::index::rtree< PointIndexEl,
|
||||||
|
boost::geometry::index::rstar<16, 4> /* ? */ >;
|
||||||
|
|
||||||
|
BoostIndex m_store;
|
||||||
|
};
|
||||||
|
|
||||||
|
PointIndex::PointIndex(): m_impl(new Impl()) {}
|
||||||
|
PointIndex::~PointIndex() {}
|
||||||
|
|
||||||
|
PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
|
||||||
|
PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
|
||||||
|
|
||||||
|
PointIndex& PointIndex::operator=(const PointIndex &cpy)
|
||||||
|
{
|
||||||
|
m_impl.reset(new Impl(*cpy.m_impl));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
PointIndex& PointIndex::operator=(PointIndex &&cpy)
|
||||||
|
{
|
||||||
|
m_impl.swap(cpy.m_impl);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PointIndex::insert(const PointIndexEl &el)
|
||||||
|
{
|
||||||
|
m_impl->m_store.insert(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PointIndex::remove(const PointIndexEl& el)
|
||||||
|
{
|
||||||
|
return m_impl->m_store.remove(el) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<PointIndexEl>
|
||||||
|
PointIndex::query(std::function<bool(const PointIndexEl &)> fn) const
|
||||||
|
{
|
||||||
|
namespace bgi = boost::geometry::index;
|
||||||
|
|
||||||
|
std::vector<PointIndexEl> ret;
|
||||||
|
m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) const
|
||||||
|
{
|
||||||
|
namespace bgi = boost::geometry::index;
|
||||||
|
std::vector<PointIndexEl> ret; ret.reserve(k);
|
||||||
|
m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t PointIndex::size() const
|
||||||
|
{
|
||||||
|
return m_impl->m_store.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn)
|
||||||
|
{
|
||||||
|
for(auto& el : m_impl->m_store) fn(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) const
|
||||||
|
{
|
||||||
|
for(const auto &el : m_impl->m_store) fn(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* **************************************************************************
|
||||||
|
* BoxIndex implementation
|
||||||
|
* ************************************************************************** */
|
||||||
|
|
||||||
|
class BoxIndex::Impl {
|
||||||
|
public:
|
||||||
|
using BoostIndex = boost::geometry::index::
|
||||||
|
rtree<BoxIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>;
|
||||||
|
|
||||||
|
BoostIndex m_store;
|
||||||
|
};
|
||||||
|
|
||||||
|
BoxIndex::BoxIndex(): m_impl(new Impl()) {}
|
||||||
|
BoxIndex::~BoxIndex() {}
|
||||||
|
|
||||||
|
BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
|
||||||
|
BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
|
||||||
|
|
||||||
|
BoxIndex& BoxIndex::operator=(const BoxIndex &cpy)
|
||||||
|
{
|
||||||
|
m_impl.reset(new Impl(*cpy.m_impl));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
BoxIndex& BoxIndex::operator=(BoxIndex &&cpy)
|
||||||
|
{
|
||||||
|
m_impl.swap(cpy.m_impl);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BoxIndex::insert(const BoxIndexEl &el)
|
||||||
|
{
|
||||||
|
m_impl->m_store.insert(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BoxIndex::remove(const BoxIndexEl& el)
|
||||||
|
{
|
||||||
|
return m_impl->m_store.remove(el) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BoxIndexEl> BoxIndex::query(const BoundingBox &qrbb,
|
||||||
|
BoxIndex::QueryType qt)
|
||||||
|
{
|
||||||
|
namespace bgi = boost::geometry::index;
|
||||||
|
|
||||||
|
std::vector<BoxIndexEl> ret; ret.reserve(m_impl->m_store.size());
|
||||||
|
|
||||||
|
switch (qt) {
|
||||||
|
case qtIntersects:
|
||||||
|
m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret));
|
||||||
|
break;
|
||||||
|
case qtWithin:
|
||||||
|
m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t BoxIndex::size() const
|
||||||
|
{
|
||||||
|
return m_impl->m_store.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BoxIndex::foreach(std::function<void (const BoxIndexEl &)> fn)
|
||||||
|
{
|
||||||
|
for(auto& el : m_impl->m_store) fn(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
}} // namespace Slic3r::sla
|
@ -73,7 +73,7 @@ public:
|
|||||||
BoxIndex& operator=(BoxIndex&&);
|
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)));
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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}
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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
@ -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);
|
||||||
|
266
src/libslic3r/SLA/SupportTreeMesher.cpp
Normal file
266
src/libslic3r/SLA/SupportTreeMesher.cpp
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
#include "SupportTreeMesher.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r { namespace sla {
|
||||||
|
|
||||||
|
Contour3D sphere(double rho, Portion portion, double fa) {
|
||||||
|
|
||||||
|
Contour3D ret;
|
||||||
|
|
||||||
|
// prohibit close to zero radius
|
||||||
|
if(rho <= 1e-6 && rho >= -1e-6) return ret;
|
||||||
|
|
||||||
|
auto& vertices = ret.points;
|
||||||
|
auto& facets = ret.faces3;
|
||||||
|
|
||||||
|
// Algorithm:
|
||||||
|
// Add points one-by-one to the sphere grid and form facets using relative
|
||||||
|
// coordinates. Sphere is composed effectively of a mesh of stacked circles.
|
||||||
|
|
||||||
|
// adjust via rounding to get an even multiple for any provided angle.
|
||||||
|
double angle = (2*PI / floor(2*PI / fa));
|
||||||
|
|
||||||
|
// Ring to be scaled to generate the steps of the sphere
|
||||||
|
std::vector<double> ring;
|
||||||
|
|
||||||
|
for (double i = 0; i < 2*PI; i+=angle) ring.emplace_back(i);
|
||||||
|
|
||||||
|
const auto sbegin = size_t(2*std::get<0>(portion)/angle);
|
||||||
|
const auto send = size_t(2*std::get<1>(portion)/angle);
|
||||||
|
|
||||||
|
const size_t steps = ring.size();
|
||||||
|
const double increment = 1.0 / double(steps);
|
||||||
|
|
||||||
|
// special case: first ring connects to 0,0,0
|
||||||
|
// insert and form facets.
|
||||||
|
if(sbegin == 0)
|
||||||
|
vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*sbegin*2.0*rho));
|
||||||
|
|
||||||
|
auto id = coord_t(vertices.size());
|
||||||
|
for (size_t i = 0; i < ring.size(); i++) {
|
||||||
|
// Fixed scaling
|
||||||
|
const double z = -rho + increment*rho*2.0 * (sbegin + 1.0);
|
||||||
|
// radius of the circle for this step.
|
||||||
|
const double r = std::sqrt(std::abs(rho*rho - z*z));
|
||||||
|
Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
|
||||||
|
vertices.emplace_back(Vec3d(b(0), b(1), z));
|
||||||
|
|
||||||
|
if (sbegin == 0)
|
||||||
|
(i == 0) ? facets.emplace_back(coord_t(ring.size()), 0, 1) :
|
||||||
|
facets.emplace_back(id - 1, 0, id);
|
||||||
|
++id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// General case: insert and form facets for each step,
|
||||||
|
// joining it to the ring below it.
|
||||||
|
for (size_t s = sbegin + 2; s < send - 1; s++) {
|
||||||
|
const double z = -rho + increment*double(s*2.0*rho);
|
||||||
|
const double r = std::sqrt(std::abs(rho*rho - z*z));
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ring.size(); i++) {
|
||||||
|
Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
|
||||||
|
vertices.emplace_back(Vec3d(b(0), b(1), z));
|
||||||
|
auto id_ringsize = coord_t(id - int(ring.size()));
|
||||||
|
if (i == 0) {
|
||||||
|
// wrap around
|
||||||
|
facets.emplace_back(id - 1, id, id + coord_t(ring.size() - 1) );
|
||||||
|
facets.emplace_back(id - 1, id_ringsize, id);
|
||||||
|
} else {
|
||||||
|
facets.emplace_back(id_ringsize - 1, id_ringsize, id);
|
||||||
|
facets.emplace_back(id - 1, id_ringsize - 1, id);
|
||||||
|
}
|
||||||
|
id++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// special case: last ring connects to 0,0,rho*2.0
|
||||||
|
// only form facets.
|
||||||
|
if(send >= size_t(2*PI / angle)) {
|
||||||
|
vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*send*2.0*rho));
|
||||||
|
for (size_t i = 0; i < ring.size(); i++) {
|
||||||
|
auto id_ringsize = coord_t(id - int(ring.size()));
|
||||||
|
if (i == 0) {
|
||||||
|
// third vertex is on the other side of the ring.
|
||||||
|
facets.emplace_back(id - 1, id_ringsize, id);
|
||||||
|
} else {
|
||||||
|
auto ci = coord_t(id_ringsize + coord_t(i));
|
||||||
|
facets.emplace_back(ci - 1, ci, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
id++;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp)
|
||||||
|
{
|
||||||
|
assert(ssteps > 0);
|
||||||
|
|
||||||
|
Contour3D ret;
|
||||||
|
|
||||||
|
auto steps = int(ssteps);
|
||||||
|
auto& points = ret.points;
|
||||||
|
auto& indices = ret.faces3;
|
||||||
|
points.reserve(2*ssteps);
|
||||||
|
double a = 2*PI/steps;
|
||||||
|
|
||||||
|
Vec3d jp = sp;
|
||||||
|
Vec3d endp = {sp(X), sp(Y), sp(Z) + h};
|
||||||
|
|
||||||
|
// Upper circle points
|
||||||
|
for(int i = 0; i < steps; ++i) {
|
||||||
|
double phi = i*a;
|
||||||
|
double ex = endp(X) + r*std::cos(phi);
|
||||||
|
double ey = endp(Y) + r*std::sin(phi);
|
||||||
|
points.emplace_back(ex, ey, endp(Z));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lower circle points
|
||||||
|
for(int i = 0; i < steps; ++i) {
|
||||||
|
double phi = i*a;
|
||||||
|
double x = jp(X) + r*std::cos(phi);
|
||||||
|
double y = jp(Y) + r*std::sin(phi);
|
||||||
|
points.emplace_back(x, y, jp(Z));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now create long triangles connecting upper and lower circles
|
||||||
|
indices.reserve(2*ssteps);
|
||||||
|
auto offs = steps;
|
||||||
|
for(int i = 0; i < steps - 1; ++i) {
|
||||||
|
indices.emplace_back(i, i + offs, offs + i + 1);
|
||||||
|
indices.emplace_back(i, offs + i + 1, i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last triangle connecting the first and last vertices
|
||||||
|
auto last = steps - 1;
|
||||||
|
indices.emplace_back(0, last, offs);
|
||||||
|
indices.emplace_back(last, offs + last, offs);
|
||||||
|
|
||||||
|
// According to the slicing algorithms, we need to aid them with generating
|
||||||
|
// a watertight body. So we create a triangle fan for the upper and lower
|
||||||
|
// ending of the cylinder to close the geometry.
|
||||||
|
points.emplace_back(jp); int ci = int(points.size() - 1);
|
||||||
|
for(int i = 0; i < steps - 1; ++i)
|
||||||
|
indices.emplace_back(i + offs + 1, i + offs, ci);
|
||||||
|
|
||||||
|
indices.emplace_back(offs, steps + offs - 1, ci);
|
||||||
|
|
||||||
|
points.emplace_back(endp); ci = int(points.size() - 1);
|
||||||
|
for(int i = 0; i < steps - 1; ++i)
|
||||||
|
indices.emplace_back(ci, i, i + 1);
|
||||||
|
|
||||||
|
indices.emplace_back(steps - 1, 0, ci);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
Contour3D pinhead(double r_pin, double r_back, double length, size_t steps)
|
||||||
|
{
|
||||||
|
assert(steps > 0);
|
||||||
|
assert(length >= 0.);
|
||||||
|
assert(r_back > 0.);
|
||||||
|
assert(r_pin > 0.);
|
||||||
|
|
||||||
|
Contour3D mesh;
|
||||||
|
|
||||||
|
// We create two spheres which will be connected with a robe that fits
|
||||||
|
// both circles perfectly.
|
||||||
|
|
||||||
|
// Set up the model detail level
|
||||||
|
const double detail = 2 * PI / steps;
|
||||||
|
|
||||||
|
// We don't generate whole circles. Instead, we generate only the
|
||||||
|
// portions which are visible (not covered by the robe) To know the
|
||||||
|
// exact portion of the bottom and top circles we need to use some
|
||||||
|
// rules of tangent circles from which we can derive (using simple
|
||||||
|
// triangles the following relations:
|
||||||
|
|
||||||
|
// The height of the whole mesh
|
||||||
|
const double h = r_back + r_pin + length;
|
||||||
|
double phi = PI / 2. - std::acos((r_back - r_pin) / h);
|
||||||
|
|
||||||
|
// To generate a whole circle we would pass a portion of (0, Pi)
|
||||||
|
// To generate only a half horizontal circle we can pass (0, Pi/2)
|
||||||
|
// The calculated phi is an offset to the half circles needed to smooth
|
||||||
|
// the transition from the circle to the robe geometry
|
||||||
|
|
||||||
|
auto &&s1 = sphere(r_back, make_portion(0, PI / 2 + phi), detail);
|
||||||
|
auto &&s2 = sphere(r_pin, make_portion(PI / 2 + phi, PI), detail);
|
||||||
|
|
||||||
|
for (auto &p : s2.points) p.z() += h;
|
||||||
|
|
||||||
|
mesh.merge(s1);
|
||||||
|
mesh.merge(s2);
|
||||||
|
|
||||||
|
for (size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size();
|
||||||
|
idx1 < s1.points.size() - 1; idx1++, idx2++) {
|
||||||
|
coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2);
|
||||||
|
coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1;
|
||||||
|
|
||||||
|
mesh.faces3.emplace_back(i1s1, i2s1, i2s2);
|
||||||
|
mesh.faces3.emplace_back(i1s1, i2s2, i1s2);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto i1s1 = coord_t(s1.points.size()) - coord_t(steps);
|
||||||
|
auto i2s1 = coord_t(s1.points.size()) - 1;
|
||||||
|
auto i1s2 = coord_t(s1.points.size());
|
||||||
|
auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1;
|
||||||
|
|
||||||
|
mesh.faces3.emplace_back(i2s2, i2s1, i1s1);
|
||||||
|
mesh.faces3.emplace_back(i1s2, i2s2, i1s1);
|
||||||
|
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
Contour3D halfcone(double baseheight,
|
||||||
|
double r_bottom,
|
||||||
|
double r_top,
|
||||||
|
const Vec3d &pos,
|
||||||
|
size_t steps)
|
||||||
|
{
|
||||||
|
assert(steps > 0);
|
||||||
|
|
||||||
|
if (baseheight <= 0 || steps <= 0) return {};
|
||||||
|
|
||||||
|
Contour3D base;
|
||||||
|
|
||||||
|
double a = 2 * PI / steps;
|
||||||
|
auto last = int(steps - 1);
|
||||||
|
Vec3d ep{pos.x(), pos.y(), pos.z() + baseheight};
|
||||||
|
for (size_t i = 0; i < steps; ++i) {
|
||||||
|
double phi = i * a;
|
||||||
|
double x = pos.x() + r_top * std::cos(phi);
|
||||||
|
double y = pos.y() + r_top * std::sin(phi);
|
||||||
|
base.points.emplace_back(x, y, ep.z());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < steps; ++i) {
|
||||||
|
double phi = i * a;
|
||||||
|
double x = pos.x() + r_bottom * std::cos(phi);
|
||||||
|
double y = pos.y() + r_bottom * std::sin(phi);
|
||||||
|
base.points.emplace_back(x, y, pos.z());
|
||||||
|
}
|
||||||
|
|
||||||
|
base.points.emplace_back(pos);
|
||||||
|
base.points.emplace_back(ep);
|
||||||
|
|
||||||
|
auto &indices = base.faces3;
|
||||||
|
auto hcenter = int(base.points.size() - 1);
|
||||||
|
auto lcenter = int(base.points.size() - 2);
|
||||||
|
auto offs = int(steps);
|
||||||
|
for (int i = 0; i < last; ++i) {
|
||||||
|
indices.emplace_back(i, i + offs, offs + i + 1);
|
||||||
|
indices.emplace_back(i, offs + i + 1, i + 1);
|
||||||
|
indices.emplace_back(i, i + 1, hcenter);
|
||||||
|
indices.emplace_back(lcenter, offs + i + 1, offs + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
indices.emplace_back(0, last, offs);
|
||||||
|
indices.emplace_back(last, offs + last, offs);
|
||||||
|
indices.emplace_back(hcenter, last, 0);
|
||||||
|
indices.emplace_back(offs, offs + last, lcenter);
|
||||||
|
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
}} // namespace Slic3r::sla
|
117
src/libslic3r/SLA/SupportTreeMesher.hpp
Normal file
117
src/libslic3r/SLA/SupportTreeMesher.hpp
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
#ifndef SUPPORTTREEMESHER_HPP
|
||||||
|
#define SUPPORTTREEMESHER_HPP
|
||||||
|
|
||||||
|
#include "libslic3r/Point.hpp"
|
||||||
|
|
||||||
|
#include "libslic3r/SLA/SupportTreeBuilder.hpp"
|
||||||
|
#include "libslic3r/SLA/Contour3D.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r { namespace sla {
|
||||||
|
|
||||||
|
using Portion = std::tuple<double, double>;
|
||||||
|
|
||||||
|
inline Portion make_portion(double a, double b)
|
||||||
|
{
|
||||||
|
return std::make_tuple(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Contour3D sphere(double rho,
|
||||||
|
Portion portion = make_portion(0., 2. * PI),
|
||||||
|
double fa = (2. * PI / 360.));
|
||||||
|
|
||||||
|
// Down facing cylinder in Z direction with arguments:
|
||||||
|
// r: radius
|
||||||
|
// h: Height
|
||||||
|
// ssteps: how many edges will create the base circle
|
||||||
|
// sp: starting point
|
||||||
|
Contour3D cylinder(double r,
|
||||||
|
double h,
|
||||||
|
size_t steps = 45,
|
||||||
|
const Vec3d &sp = Vec3d::Zero());
|
||||||
|
|
||||||
|
Contour3D pinhead(double r_pin, double r_back, double length, size_t steps = 45);
|
||||||
|
|
||||||
|
Contour3D halfcone(double baseheight,
|
||||||
|
double r_bottom,
|
||||||
|
double r_top,
|
||||||
|
const Vec3d &pt = Vec3d::Zero(),
|
||||||
|
size_t steps = 45);
|
||||||
|
|
||||||
|
inline Contour3D get_mesh(const Head &h, size_t steps)
|
||||||
|
{
|
||||||
|
Contour3D mesh = pinhead(h.r_pin_mm, h.r_back_mm, h.width_mm, steps);
|
||||||
|
|
||||||
|
for(auto& p : mesh.points) p.z() -= (h.fullwidth() - h.r_back_mm);
|
||||||
|
|
||||||
|
using Quaternion = Eigen::Quaternion<double>;
|
||||||
|
|
||||||
|
// We rotate the head to the specified direction. The head's pointing
|
||||||
|
// side is facing upwards so this means that it would hold a support
|
||||||
|
// point with a normal pointing straight down. This is the reason of
|
||||||
|
// the -1 z coordinate
|
||||||
|
auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, h.dir);
|
||||||
|
|
||||||
|
for(auto& p : mesh.points) p = quatern * p + h.pos;
|
||||||
|
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Contour3D get_mesh(const Pillar &p, size_t steps)
|
||||||
|
{
|
||||||
|
if(p.height > EPSILON) { // Endpoint is below the starting point
|
||||||
|
// We just create a bridge geometry with the pillar parameters and
|
||||||
|
// move the data.
|
||||||
|
return cylinder(p.r, p.height, steps, p.endpoint());
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Contour3D get_mesh(const Pedestal &p, size_t steps)
|
||||||
|
{
|
||||||
|
return halfcone(p.height, p.r_bottom, p.r_top, p.pos, steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Contour3D get_mesh(const Junction &j, size_t steps)
|
||||||
|
{
|
||||||
|
Contour3D mesh = sphere(j.r, make_portion(0, PI), 2 *PI / steps);
|
||||||
|
for(auto& p : mesh.points) p += j.pos;
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Contour3D get_mesh(const Bridge &br, size_t steps)
|
||||||
|
{
|
||||||
|
using Quaternion = Eigen::Quaternion<double>;
|
||||||
|
Vec3d v = (br.endp - br.startp);
|
||||||
|
Vec3d dir = v.normalized();
|
||||||
|
double d = v.norm();
|
||||||
|
|
||||||
|
Contour3D mesh = cylinder(br.r, d, steps);
|
||||||
|
|
||||||
|
auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir);
|
||||||
|
for(auto& p : mesh.points) p = quater * p + br.startp;
|
||||||
|
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Contour3D get_mesh(const DiffBridge &br, size_t steps)
|
||||||
|
{
|
||||||
|
double h = br.get_length();
|
||||||
|
Contour3D mesh = halfcone(h, br.r, br.end_r, Vec3d::Zero(), steps);
|
||||||
|
|
||||||
|
using Quaternion = Eigen::Quaternion<double>;
|
||||||
|
|
||||||
|
// We rotate the head to the specified direction. The head's pointing
|
||||||
|
// side is facing upwards so this means that it would hold a support
|
||||||
|
// point with a normal pointing straight down. This is the reason of
|
||||||
|
// the -1 z coordinate
|
||||||
|
auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, 1}, br.get_dir());
|
||||||
|
|
||||||
|
for(auto& p : mesh.points) p = quatern * p + br.startp;
|
||||||
|
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
}} // namespace Slic3r::sla
|
||||||
|
|
||||||
|
#endif // SUPPORTTREEMESHER_HPP
|
@ -35,13 +35,16 @@ bool is_zero_elevation(const SLAPrintObjectConfig &c)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compile the argument for support creation from the static print config.
|
// 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"
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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());
|
||||||
|
@ -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
|
||||||
|
@ -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")));
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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());
|
||||||
|
@ -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");
|
||||||
|
@ -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>());
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
918
src/slic3r/GUI/NotificationManager.cpp
Normal file
918
src/slic3r/GUI/NotificationManager.cpp
Normal file
@ -0,0 +1,918 @@
|
|||||||
|
#include "NotificationManager.hpp"
|
||||||
|
|
||||||
|
#include "GUI_App.hpp"
|
||||||
|
#include "Plater.hpp"
|
||||||
|
#include "GLCanvas3D.hpp"
|
||||||
|
#include "ImGuiWrapper.hpp"
|
||||||
|
|
||||||
|
#include "wxExtensions.hpp"
|
||||||
|
|
||||||
|
#include <boost/algorithm/string.hpp>
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <wx/glcanvas.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#define NOTIFICATION_MAX_MOVE 3.0f
|
||||||
|
|
||||||
|
#define GAP_WIDTH 10.0f
|
||||||
|
#define SPACE_RIGHT_PANEL 10.0f
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace GUI {
|
||||||
|
|
||||||
|
wxDEFINE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClickedEvent);
|
||||||
|
wxDEFINE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent);
|
||||||
|
wxDEFINE_EVENT(EVT_PRESET_UPDATE_AVIABLE_CLICKED, PresetUpdateAviableClickedEvent);
|
||||||
|
|
||||||
|
namespace Notifications_Internal{
|
||||||
|
void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, float current_fade_opacity)
|
||||||
|
{
|
||||||
|
if (fading_out)
|
||||||
|
ImGui::PushStyleColor(idx, ImVec4(col.x, col.y, col.z, col.w * current_fade_opacity));
|
||||||
|
else
|
||||||
|
ImGui::PushStyleColor(idx, col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//ScalableBitmap bmp_icon;
|
||||||
|
//------PopNotification--------
|
||||||
|
NotificationManager::PopNotification::PopNotification(const NotificationData &n, const int id, wxEvtHandler* evt_handler) :
|
||||||
|
m_data (n)
|
||||||
|
, m_id (id)
|
||||||
|
, m_remaining_time (n.duration)
|
||||||
|
, m_last_remaining_time (n.duration)
|
||||||
|
, m_counting_down (n.duration != 0)
|
||||||
|
, m_text1 (n.text1)
|
||||||
|
, m_hypertext (n.hypertext)
|
||||||
|
, m_text2 (n.text2)
|
||||||
|
, m_evt_handler (evt_handler)
|
||||||
|
{
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
NotificationManager::PopNotification::~PopNotification()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
NotificationManager::PopNotification::RenderResult NotificationManager::PopNotification::render(GLCanvas3D& canvas, const float& initial_y)
|
||||||
|
{
|
||||||
|
if (m_finished)
|
||||||
|
return RenderResult::Finished;
|
||||||
|
if (m_close_pending) {
|
||||||
|
// request of extra frame will be done in caller function by ret val ClosePending
|
||||||
|
m_finished = true;
|
||||||
|
return RenderResult::ClosePending;
|
||||||
|
}
|
||||||
|
if (m_hidden) {
|
||||||
|
m_top_y = initial_y - GAP_WIDTH;
|
||||||
|
return RenderResult::Static;
|
||||||
|
}
|
||||||
|
RenderResult ret_val = m_counting_down ? RenderResult::Countdown : RenderResult::Static;
|
||||||
|
Size cnv_size = canvas.get_canvas_size();
|
||||||
|
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
||||||
|
bool shown = true;
|
||||||
|
std::string name;
|
||||||
|
ImVec2 mouse_pos = ImGui::GetMousePos();
|
||||||
|
|
||||||
|
if (m_line_height != ImGui::CalcTextSize("A").y)
|
||||||
|
init();
|
||||||
|
|
||||||
|
set_next_window_size(imgui);
|
||||||
|
|
||||||
|
//top y of window
|
||||||
|
m_top_y = initial_y + m_window_height;
|
||||||
|
//top right position
|
||||||
|
ImVec2 win_pos(1.0f * (float)cnv_size.get_width() - SPACE_RIGHT_PANEL, 1.0f * (float)cnv_size.get_height() - m_top_y);
|
||||||
|
imgui.set_next_window_pos(win_pos.x, win_pos.y, ImGuiCond_Always, 1.0f, 0.0f);
|
||||||
|
imgui.set_next_window_size(m_window_width, m_window_height, ImGuiCond_Always);
|
||||||
|
|
||||||
|
//find if hovered
|
||||||
|
if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y&& mouse_pos.y < win_pos.y + m_window_height)
|
||||||
|
{
|
||||||
|
ImGui::SetNextWindowFocus();
|
||||||
|
ret_val = RenderResult::Hovered;
|
||||||
|
//reset fading
|
||||||
|
m_fading_out = false;
|
||||||
|
m_current_fade_opacity = 1.f;
|
||||||
|
m_remaining_time = m_data.duration;
|
||||||
|
m_countdown_frame = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_counting_down && m_remaining_time < 0)
|
||||||
|
m_close_pending = true;
|
||||||
|
|
||||||
|
if (m_close_pending) {
|
||||||
|
// request of extra frame will be done in caller function by ret val ClosePending
|
||||||
|
m_finished = true;
|
||||||
|
return RenderResult::ClosePending;
|
||||||
|
}
|
||||||
|
|
||||||
|
// color change based on fading out
|
||||||
|
bool fading_pop = false;
|
||||||
|
if (m_fading_out) {
|
||||||
|
if (!m_paused)
|
||||||
|
m_current_fade_opacity -= 1.f / ((m_fading_time + 1.f) * 60.f);
|
||||||
|
Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
|
||||||
|
Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity);
|
||||||
|
fading_pop = true;
|
||||||
|
}
|
||||||
|
// background color
|
||||||
|
if (m_is_gray) {
|
||||||
|
ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f);
|
||||||
|
Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
|
||||||
|
} else if (m_data.level == NotificationLevel::ErrorNotification) {
|
||||||
|
ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
|
||||||
|
backcolor.x += 0.3f;
|
||||||
|
Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
|
||||||
|
} else if (m_data.level == NotificationLevel::WarningNotification) {
|
||||||
|
ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
|
||||||
|
backcolor.x += 0.3f;
|
||||||
|
backcolor.y += 0.15f;
|
||||||
|
Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
//name of window - probably indentifies window and is shown so last_end add whitespaces according to id
|
||||||
|
for (size_t i = 0; i < m_id; i++)
|
||||||
|
name += " ";
|
||||||
|
if (imgui.begin(name, &shown, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar )) {
|
||||||
|
if (shown) {
|
||||||
|
|
||||||
|
ImVec2 win_size = ImGui::GetWindowSize();
|
||||||
|
|
||||||
|
|
||||||
|
//FIXME: dont forget to us this for texts
|
||||||
|
//GUI::format(_utf8(L()));
|
||||||
|
|
||||||
|
/*
|
||||||
|
//countdown numbers
|
||||||
|
ImGui::SetCursorPosX(15);
|
||||||
|
ImGui::SetCursorPosY(15);
|
||||||
|
imgui.text(std::to_string(m_remaining_time).c_str());
|
||||||
|
*/
|
||||||
|
if(m_counting_down)
|
||||||
|
render_countdown(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
|
||||||
|
render_left_sign(imgui);
|
||||||
|
render_text(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
|
||||||
|
render_close_button(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
|
||||||
|
if (m_multiline && m_lines_count > 3)
|
||||||
|
render_minimize_button(imgui, win_pos.x, win_pos.y);
|
||||||
|
} else {
|
||||||
|
// the user clicked on the [X] button ( ImGuiWindowFlags_NoTitleBar means theres no [X] button)
|
||||||
|
m_close_pending = true;
|
||||||
|
canvas.set_as_dirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imgui.end();
|
||||||
|
|
||||||
|
if (fading_pop) {
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
if (m_is_gray)
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
else if (m_data.level == NotificationLevel::ErrorNotification)
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
else if (m_data.level == NotificationLevel::WarningNotification)
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
return ret_val;
|
||||||
|
}
|
||||||
|
void NotificationManager::PopNotification::init()
|
||||||
|
{
|
||||||
|
std::string text = m_text1 + " " + m_hypertext;
|
||||||
|
int last_end = 0;
|
||||||
|
m_lines_count = 0;
|
||||||
|
|
||||||
|
//determine line width
|
||||||
|
m_line_height = ImGui::CalcTextSize("A").y;
|
||||||
|
|
||||||
|
m_left_indentation = m_line_height;
|
||||||
|
if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) {
|
||||||
|
std::string text;
|
||||||
|
text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker);
|
||||||
|
float picture_width = ImGui::CalcTextSize(text.c_str()).x;
|
||||||
|
m_left_indentation = picture_width + m_line_height / 2;
|
||||||
|
}
|
||||||
|
m_window_width_offset = m_left_indentation + m_line_height * 2;
|
||||||
|
m_window_width = m_line_height * 25;
|
||||||
|
|
||||||
|
// count lines
|
||||||
|
m_endlines.clear();
|
||||||
|
while (last_end < text.length() - 1)
|
||||||
|
{
|
||||||
|
int next_hard_end = text.find_first_of('\n', last_end);
|
||||||
|
if (next_hard_end > 0 && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset) {
|
||||||
|
//next line is ended by '/n'
|
||||||
|
m_endlines.push_back(next_hard_end);
|
||||||
|
last_end = next_hard_end + 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// find next suitable endline
|
||||||
|
if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - 3.5f * m_line_height) {// m_window_width_offset) {
|
||||||
|
// more than one line till end
|
||||||
|
int next_space = text.find_first_of(' ', last_end);
|
||||||
|
if (next_space > 0) {
|
||||||
|
int next_space_candidate = text.find_first_of(' ', next_space + 1);
|
||||||
|
while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset) {
|
||||||
|
next_space = next_space_candidate;
|
||||||
|
next_space_candidate = text.find_first_of(' ', next_space + 1);
|
||||||
|
}
|
||||||
|
m_endlines.push_back(next_space);
|
||||||
|
last_end = next_space + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_endlines.push_back(text.length());
|
||||||
|
last_end = text.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
m_lines_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui)
|
||||||
|
{
|
||||||
|
if (m_multiline) {
|
||||||
|
m_window_height = m_lines_count * m_line_height;
|
||||||
|
}else
|
||||||
|
{
|
||||||
|
m_window_height = 2 * m_line_height;
|
||||||
|
}
|
||||||
|
m_window_height += 1 * m_line_height; // top and bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||||
|
{
|
||||||
|
ImVec2 win_size(win_size_x, win_size_y);
|
||||||
|
ImVec2 win_pos(win_pos_x, win_pos_y);
|
||||||
|
float x_offset = m_left_indentation;
|
||||||
|
std::string fulltext = m_text1 + m_hypertext; //+ m_text2;
|
||||||
|
ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str());
|
||||||
|
// text posistions are calculated by lines count
|
||||||
|
// large texts has "more" button or are displayed whole
|
||||||
|
// smaller texts are divided as one liners and two liners
|
||||||
|
if (m_lines_count > 2) {
|
||||||
|
if (m_multiline) {
|
||||||
|
|
||||||
|
int last_end = 0;
|
||||||
|
float starting_y = m_line_height/2;//10;
|
||||||
|
float shift_y = m_line_height;// -m_line_height / 20;
|
||||||
|
for (size_t i = 0; i < m_lines_count; i++) {
|
||||||
|
std::string line = m_text1.substr(last_end , m_endlines[i] - last_end);
|
||||||
|
last_end = m_endlines[i] + 1;
|
||||||
|
ImGui::SetCursorPosX(x_offset);
|
||||||
|
ImGui::SetCursorPosY(starting_y + i * shift_y);
|
||||||
|
imgui.text(line.c_str());
|
||||||
|
}
|
||||||
|
//hyperlink text
|
||||||
|
if (!m_hypertext.empty())
|
||||||
|
{
|
||||||
|
render_hypertext(imgui, x_offset + ImGui::CalcTextSize(m_text1.substr(m_endlines[m_lines_count - 2] + 1, m_endlines[m_lines_count - 1] - m_endlines[m_lines_count - 2] - 1).c_str()).x, starting_y + (m_lines_count - 1) * shift_y, m_hypertext);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// line1
|
||||||
|
ImGui::SetCursorPosX(x_offset);
|
||||||
|
ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2);
|
||||||
|
imgui.text(m_text1.substr(0, m_endlines[0]).c_str());
|
||||||
|
// line2
|
||||||
|
std::string line = m_text1.substr(m_endlines[0] + 1, m_endlines[1] - m_endlines[0] - 1);
|
||||||
|
if (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x)
|
||||||
|
{
|
||||||
|
line = line.substr(0, line.length() - 6);
|
||||||
|
line += "..";
|
||||||
|
}else
|
||||||
|
line += " ";
|
||||||
|
ImGui::SetCursorPosX(x_offset);
|
||||||
|
ImGui::SetCursorPosY(win_size.y / 2 + win_size.y / 6 - m_line_height / 2);
|
||||||
|
imgui.text(line.c_str());
|
||||||
|
// "More" hypertext
|
||||||
|
render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x, win_size.y / 2 + win_size.y / 6 - m_line_height / 2, _u8L("More"), true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//text 1
|
||||||
|
float cursor_y = win_size.y / 2 - text_size.y / 2;
|
||||||
|
float cursor_x = x_offset;
|
||||||
|
if(m_lines_count > 1) {
|
||||||
|
// line1
|
||||||
|
ImGui::SetCursorPosX(x_offset);
|
||||||
|
ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2);
|
||||||
|
imgui.text(m_text1.substr(0, m_endlines[0]).c_str());
|
||||||
|
// line2
|
||||||
|
std::string line = m_text1.substr(m_endlines[0] + 1);
|
||||||
|
cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2;
|
||||||
|
ImGui::SetCursorPosX(x_offset);
|
||||||
|
ImGui::SetCursorPosY(cursor_y);
|
||||||
|
imgui.text(line.c_str());
|
||||||
|
cursor_x = x_offset + ImGui::CalcTextSize(line.c_str()).x;
|
||||||
|
} else {
|
||||||
|
ImGui::SetCursorPosX(x_offset);
|
||||||
|
ImGui::SetCursorPosY(cursor_y);
|
||||||
|
imgui.text(m_text1.c_str());
|
||||||
|
cursor_x = x_offset + ImGui::CalcTextSize(m_text1.c_str()).x;
|
||||||
|
}
|
||||||
|
//hyperlink text
|
||||||
|
if (!m_hypertext.empty())
|
||||||
|
{
|
||||||
|
render_hypertext(imgui, cursor_x + 4, cursor_y, m_hypertext);
|
||||||
|
}
|
||||||
|
|
||||||
|
//notification text 2
|
||||||
|
//text 2 is suposed to be after the hyperlink - currently it is not used
|
||||||
|
/*
|
||||||
|
if (!m_text2.empty())
|
||||||
|
{
|
||||||
|
ImVec2 part_size = ImGui::CalcTextSize(m_hypertext.c_str());
|
||||||
|
ImGui::SetCursorPosX(win_size.x / 2 + text_size.x / 2 - part_size.x + 8 - x_offset);
|
||||||
|
ImGui::SetCursorPosY(cursor_y);
|
||||||
|
imgui.text(m_text2.c_str());
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, const float text_x, const float text_y, const std::string text, bool more)
|
||||||
|
{
|
||||||
|
//invisible button
|
||||||
|
ImVec2 part_size = ImGui::CalcTextSize(text.c_str());
|
||||||
|
ImGui::SetCursorPosX(text_x -4);
|
||||||
|
ImGui::SetCursorPosY(text_y -5);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
||||||
|
if (imgui.button(" ", part_size.x + 6, part_size.y + 10))
|
||||||
|
{
|
||||||
|
if (more)
|
||||||
|
{
|
||||||
|
m_multiline = true;
|
||||||
|
set_next_window_size(imgui);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
on_text_click();
|
||||||
|
m_close_pending = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
|
||||||
|
//hover color
|
||||||
|
ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
|
||||||
|
if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly))
|
||||||
|
orange_color.y += 0.2f;
|
||||||
|
|
||||||
|
//text
|
||||||
|
Notifications_Internal::push_style_color(ImGuiCol_Text, orange_color, m_fading_out, m_current_fade_opacity);
|
||||||
|
ImGui::SetCursorPosX(text_x);
|
||||||
|
ImGui::SetCursorPosY(text_y);
|
||||||
|
imgui.text(text.c_str());
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
|
||||||
|
//underline
|
||||||
|
ImVec2 lineEnd = ImGui::GetItemRectMax();
|
||||||
|
lineEnd.y -= 2;
|
||||||
|
ImVec2 lineStart = lineEnd;
|
||||||
|
lineStart.x = ImGui::GetItemRectMin().x;
|
||||||
|
ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.w * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f))));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||||
|
{
|
||||||
|
ImVec2 win_size(win_size_x, win_size_y);
|
||||||
|
ImVec2 win_pos(win_pos_x, win_pos_y);
|
||||||
|
ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
|
||||||
|
orange_color.w = 0.8f;
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||||
|
Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
|
||||||
|
Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
||||||
|
|
||||||
|
|
||||||
|
//button - if part if treggered
|
||||||
|
std::string button_text;
|
||||||
|
button_text = ImGui::CloseIconMarker;
|
||||||
|
|
||||||
|
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y),
|
||||||
|
ImVec2(win_pos.x, win_pos.y + win_size.y - (m_multiline? 2 * m_line_height : 0)),
|
||||||
|
true))
|
||||||
|
{
|
||||||
|
button_text = ImGui::CloseIconHoverMarker;
|
||||||
|
}
|
||||||
|
ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
|
||||||
|
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
|
||||||
|
ImGui::SetCursorPosX(win_size.x - m_line_height * 2.25f);
|
||||||
|
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y/2);
|
||||||
|
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
|
||||||
|
{
|
||||||
|
m_close_pending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//invisible large button
|
||||||
|
ImGui::SetCursorPosX(win_size.x - win_size.x / 10.f);
|
||||||
|
ImGui::SetCursorPosY(0);
|
||||||
|
if (imgui.button(" ", win_size.x / 10.f, win_size.y - (m_multiline ? 2 * m_line_height : 0)))
|
||||||
|
{
|
||||||
|
m_close_pending = true;
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
void NotificationManager::PopNotification::render_countdown(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
ImVec2 win_size(win_size_x, win_size_y);
|
||||||
|
ImVec2 win_pos(win_pos_x, win_pos_y);
|
||||||
|
|
||||||
|
//countdown dots
|
||||||
|
std::string dot_text;
|
||||||
|
dot_text = m_remaining_time <= (float)m_data.duration / 4 * 3 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker;
|
||||||
|
ImGui::SetCursorPosX(win_size.x - m_line_height);
|
||||||
|
//ImGui::SetCursorPosY(win_size.y / 2 - 24);
|
||||||
|
ImGui::SetCursorPosY(0);
|
||||||
|
imgui.text(dot_text.c_str());
|
||||||
|
|
||||||
|
dot_text = m_remaining_time < m_data.duration / 2 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker;
|
||||||
|
ImGui::SetCursorPosX(win_size.x - m_line_height);
|
||||||
|
//ImGui::SetCursorPosY(win_size.y / 2 - 9);
|
||||||
|
ImGui::SetCursorPosY(win_size.y / 2 - m_line_height / 2);
|
||||||
|
imgui.text(dot_text.c_str());
|
||||||
|
|
||||||
|
dot_text = m_remaining_time <= m_data.duration / 4 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker;
|
||||||
|
ImGui::SetCursorPosX(win_size.x - m_line_height);
|
||||||
|
//ImGui::SetCursorPosY(win_size.y / 2 + 6);
|
||||||
|
ImGui::SetCursorPosY(win_size.y - m_line_height);
|
||||||
|
imgui.text(dot_text.c_str());
|
||||||
|
*/
|
||||||
|
if (!m_fading_out && m_remaining_time <= m_data.duration / 4) {
|
||||||
|
m_fading_out = true;
|
||||||
|
m_fading_time = m_remaining_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_last_remaining_time != m_remaining_time) {
|
||||||
|
m_last_remaining_time = m_remaining_time;
|
||||||
|
m_countdown_frame = 0;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
//countdown line
|
||||||
|
ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
|
||||||
|
float invisible_length = ((float)(m_data.duration - m_remaining_time) / (float)m_data.duration * win_size_x);
|
||||||
|
invisible_length -= win_size_x / ((float)m_data.duration * 60.f) * (60 - m_countdown_frame);
|
||||||
|
ImVec2 lineEnd = ImVec2(win_pos_x - invisible_length, win_pos_y + win_size_y - 5);
|
||||||
|
ImVec2 lineStart = ImVec2(win_pos_x - win_size_x, win_pos_y + win_size_y - 5);
|
||||||
|
ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.picture_width * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f))), 2.f);
|
||||||
|
if (!m_paused)
|
||||||
|
m_countdown_frame++;
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui)
|
||||||
|
{
|
||||||
|
if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) {
|
||||||
|
std::string text;
|
||||||
|
text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker);
|
||||||
|
ImGui::SetCursorPosX(m_line_height / 3);
|
||||||
|
ImGui::SetCursorPosY(m_window_height / 2 - m_line_height / 2);
|
||||||
|
imgui.text(text.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y)
|
||||||
|
{
|
||||||
|
ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
|
||||||
|
orange_color.w = 0.8f;
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||||
|
Notifications_Internal::push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
|
||||||
|
Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
|
||||||
|
Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
|
||||||
|
|
||||||
|
|
||||||
|
//button - if part if treggered
|
||||||
|
std::string button_text;
|
||||||
|
button_text = ImGui::CloseIconMarker;
|
||||||
|
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos_x - m_window_width / 10.f, win_pos_y + m_window_height - 2 * m_line_height + 1),
|
||||||
|
ImVec2(win_pos_x, win_pos_y + m_window_height),
|
||||||
|
true))
|
||||||
|
{
|
||||||
|
button_text = ImGui::CloseIconHoverMarker;
|
||||||
|
}
|
||||||
|
ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
|
||||||
|
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
|
||||||
|
ImGui::SetCursorPosX(m_window_width - m_line_height * 2.25f);
|
||||||
|
ImGui::SetCursorPosY(m_window_height - button_size.y - 5);
|
||||||
|
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
|
||||||
|
{
|
||||||
|
m_multiline = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
void NotificationManager::PopNotification::on_text_click()
|
||||||
|
{
|
||||||
|
switch (m_data.type) {
|
||||||
|
case NotificationType::ExportToRemovableFinished :
|
||||||
|
assert(m_evt_handler != nullptr);
|
||||||
|
if (m_evt_handler != nullptr)
|
||||||
|
wxPostEvent(m_evt_handler, EjectDriveNotificationClickedEvent(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED));
|
||||||
|
break;
|
||||||
|
case NotificationType::SlicingComplete :
|
||||||
|
//wxGetApp().plater()->export_gcode(false);
|
||||||
|
assert(m_evt_handler != nullptr);
|
||||||
|
if (m_evt_handler != nullptr)
|
||||||
|
wxPostEvent(m_evt_handler, ExportGcodeNotificationClickedEvent(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED));
|
||||||
|
break;
|
||||||
|
case NotificationType::PresetUpdateAviable :
|
||||||
|
//wxGetApp().plater()->export_gcode(false);
|
||||||
|
assert(m_evt_handler != nullptr);
|
||||||
|
if (m_evt_handler != nullptr)
|
||||||
|
wxPostEvent(m_evt_handler, PresetUpdateAviableClickedEvent(EVT_PRESET_UPDATE_AVIABLE_CLICKED));
|
||||||
|
break;
|
||||||
|
case NotificationType::NewAppAviable:
|
||||||
|
wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::PopNotification::update(const NotificationData& n)
|
||||||
|
{
|
||||||
|
m_text1 = n.text1;
|
||||||
|
m_hypertext = n.hypertext;
|
||||||
|
m_text2 = n.text2;
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
bool NotificationManager::PopNotification::compare_text(const std::string& text)
|
||||||
|
{
|
||||||
|
std::string t1(m_text1);
|
||||||
|
std::string t2(text);
|
||||||
|
t1.erase(std::remove_if(t1.begin(), t1.end(), ::isspace), t1.end());
|
||||||
|
t2.erase(std::remove_if(t2.begin(), t2.end(), ::isspace), t2.end());
|
||||||
|
if (t1.compare(t2) == 0)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationManager::SlicingCompleteLargeNotification::SlicingCompleteLargeNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler, bool large) :
|
||||||
|
NotificationManager::PopNotification(n, id, evt_handler)
|
||||||
|
{
|
||||||
|
set_large(large);
|
||||||
|
}
|
||||||
|
void NotificationManager::SlicingCompleteLargeNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||||
|
{
|
||||||
|
if (!m_is_large)
|
||||||
|
PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||||
|
else {
|
||||||
|
ImVec2 win_size(win_size_x, win_size_y);
|
||||||
|
ImVec2 win_pos(win_pos_x, win_pos_y);
|
||||||
|
|
||||||
|
ImVec2 text1_size = ImGui::CalcTextSize(m_text1.c_str());
|
||||||
|
float x_offset = m_left_indentation;
|
||||||
|
std::string fulltext = m_text1 + m_hypertext + m_text2;
|
||||||
|
ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str());
|
||||||
|
float cursor_y = win_size.y / 2 - text_size.y / 2;
|
||||||
|
if (m_has_print_info) {
|
||||||
|
x_offset = 20;
|
||||||
|
cursor_y = win_size.y / 2 + win_size.y / 6 - text_size.y / 2;
|
||||||
|
ImGui::SetCursorPosX(x_offset);
|
||||||
|
ImGui::SetCursorPosY(cursor_y);
|
||||||
|
imgui.text(m_print_info.c_str());
|
||||||
|
cursor_y = win_size.y / 2 - win_size.y / 6 - text_size.y / 2;
|
||||||
|
}
|
||||||
|
ImGui::SetCursorPosX(x_offset);
|
||||||
|
ImGui::SetCursorPosY(cursor_y);
|
||||||
|
imgui.text(m_text1.c_str());
|
||||||
|
|
||||||
|
render_hypertext(imgui, x_offset + text1_size.x + 4, cursor_y, m_hypertext);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::SlicingCompleteLargeNotification::set_print_info(std::string info)
|
||||||
|
{
|
||||||
|
m_print_info = info;
|
||||||
|
m_has_print_info = true;
|
||||||
|
if(m_is_large)
|
||||||
|
m_lines_count = 2;
|
||||||
|
}
|
||||||
|
void NotificationManager::SlicingCompleteLargeNotification::set_large(bool l)
|
||||||
|
{
|
||||||
|
m_is_large = l;
|
||||||
|
m_counting_down = !l;
|
||||||
|
m_hypertext = l ? _u8L("Export G-Code.") : std::string();
|
||||||
|
m_hidden = !l;
|
||||||
|
}
|
||||||
|
//------NotificationManager--------
|
||||||
|
NotificationManager::NotificationManager(wxEvtHandler* evt_handler) :
|
||||||
|
m_evt_handler(evt_handler)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
NotificationManager::~NotificationManager()
|
||||||
|
{
|
||||||
|
for (PopNotification* notification : m_pop_notifications)
|
||||||
|
{
|
||||||
|
delete notification;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::push_notification(const NotificationType type, GLCanvas3D& canvas, int timestamp)
|
||||||
|
{
|
||||||
|
auto it = std::find_if(basic_notifications.begin(), basic_notifications.end(),
|
||||||
|
boost::bind(&NotificationData::type, _1) == type);
|
||||||
|
if (it != basic_notifications.end())
|
||||||
|
push_notification_data( *it, canvas, timestamp);
|
||||||
|
}
|
||||||
|
void NotificationManager::push_notification(const std::string& text, GLCanvas3D& canvas, int timestamp)
|
||||||
|
{
|
||||||
|
push_notification_data({ NotificationType::CustomNotification, NotificationLevel::RegularNotification, 10, text }, canvas, timestamp );
|
||||||
|
}
|
||||||
|
void NotificationManager::push_notification(const std::string& text, NotificationManager::NotificationLevel level, GLCanvas3D& canvas, int timestamp)
|
||||||
|
{
|
||||||
|
switch (level)
|
||||||
|
{
|
||||||
|
case Slic3r::GUI::NotificationManager::NotificationLevel::RegularNotification:
|
||||||
|
push_notification_data({ NotificationType::CustomNotification, level, 10, text }, canvas, timestamp);
|
||||||
|
break;
|
||||||
|
case Slic3r::GUI::NotificationManager::NotificationLevel::ErrorNotification:
|
||||||
|
push_notification_data({ NotificationType::CustomNotification, level, 0, text }, canvas, timestamp);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case Slic3r::GUI::NotificationManager::NotificationLevel::ImportantNotification:
|
||||||
|
push_notification_data({ NotificationType::CustomNotification, level, 0, text }, canvas, timestamp);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::push_slicing_error_notification(const std::string& text, GLCanvas3D& canvas)
|
||||||
|
{
|
||||||
|
set_all_slicing_errors_gray(false);
|
||||||
|
push_notification_data({ NotificationType::SlicingError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, canvas, 0);
|
||||||
|
close_notification_of_type(NotificationType::SlicingComplete);
|
||||||
|
}
|
||||||
|
void NotificationManager::push_slicing_warning_notification(const std::string& text, bool gray, GLCanvas3D& canvas, size_t oid, int warning_step)
|
||||||
|
{
|
||||||
|
NotificationData data { NotificationType::SlicingWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text };
|
||||||
|
|
||||||
|
NotificationManager::SlicingWarningNotification* notification = new NotificationManager::SlicingWarningNotification(data, m_next_id++, m_evt_handler);
|
||||||
|
notification->set_object_id(oid);
|
||||||
|
notification->set_warning_step(warning_step);
|
||||||
|
if
|
||||||
|
(push_notification_data(notification, canvas, 0)) {
|
||||||
|
notification->set_gray(gray);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
delete notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
void NotificationManager::push_plater_error_notification(const std::string& text, GLCanvas3D& canvas)
|
||||||
|
{
|
||||||
|
push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, canvas, 0);
|
||||||
|
}
|
||||||
|
void NotificationManager::push_plater_warning_notification(const std::string& text, GLCanvas3D& canvas)
|
||||||
|
{
|
||||||
|
push_notification_data({ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }, canvas, 0);
|
||||||
|
}
|
||||||
|
void NotificationManager::close_plater_error_notification()
|
||||||
|
{
|
||||||
|
for (PopNotification* notification : m_pop_notifications) {
|
||||||
|
if (notification->get_type() == NotificationType::PlaterError) {
|
||||||
|
notification->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::close_plater_warning_notification(const std::string& text)
|
||||||
|
{
|
||||||
|
for (PopNotification* notification : m_pop_notifications) {
|
||||||
|
if (notification->get_type() == NotificationType::PlaterWarning && notification->compare_text(_u8L("WARNING:") + "\n" + text)) {
|
||||||
|
notification->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::set_all_slicing_errors_gray(bool g)
|
||||||
|
{
|
||||||
|
for (PopNotification* notification : m_pop_notifications) {
|
||||||
|
if (notification->get_type() == NotificationType::SlicingError) {
|
||||||
|
notification->set_gray(g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::set_all_slicing_warnings_gray(bool g)
|
||||||
|
{
|
||||||
|
for (PopNotification* notification : m_pop_notifications) {
|
||||||
|
if (notification->get_type() == NotificationType::SlicingWarning) {
|
||||||
|
notification->set_gray(g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::set_slicing_warning_gray(const std::string& text, bool g)
|
||||||
|
{
|
||||||
|
for (PopNotification* notification : m_pop_notifications) {
|
||||||
|
if (notification->get_type() == NotificationType::SlicingWarning && notification->compare_text(text)) {
|
||||||
|
notification->set_gray(g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::close_slicing_errors_and_warnings()
|
||||||
|
{
|
||||||
|
for (PopNotification* notification : m_pop_notifications) {
|
||||||
|
if (notification->get_type() == NotificationType::SlicingError || notification->get_type() == NotificationType::SlicingWarning) {
|
||||||
|
notification->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::push_slicing_complete_notification(GLCanvas3D& canvas, int timestamp, bool large)
|
||||||
|
{
|
||||||
|
std::string hypertext;
|
||||||
|
int time = 10;
|
||||||
|
if(large)
|
||||||
|
{
|
||||||
|
hypertext = _u8L("Export G-Code.");
|
||||||
|
time = 0;
|
||||||
|
}
|
||||||
|
NotificationData data{ NotificationType::SlicingComplete, NotificationLevel::RegularNotification, time, _u8L("Slicing finished."), hypertext };
|
||||||
|
|
||||||
|
NotificationManager::SlicingCompleteLargeNotification* notification = new NotificationManager::SlicingCompleteLargeNotification(data, m_next_id++, m_evt_handler, large);
|
||||||
|
if (push_notification_data(notification, canvas, timestamp)) {
|
||||||
|
} else {
|
||||||
|
delete notification;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::set_slicing_complete_print_time(std::string info)
|
||||||
|
{
|
||||||
|
for (PopNotification* notification : m_pop_notifications) {
|
||||||
|
if (notification->get_type() == NotificationType::SlicingComplete) {
|
||||||
|
dynamic_cast<SlicingCompleteLargeNotification*>(notification)->set_print_info(info);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::set_slicing_complete_large(bool large)
|
||||||
|
{
|
||||||
|
for (PopNotification* notification : m_pop_notifications) {
|
||||||
|
if (notification->get_type() == NotificationType::SlicingComplete) {
|
||||||
|
dynamic_cast<SlicingCompleteLargeNotification*>(notification)->set_large(large);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::close_notification_of_type(const NotificationType type)
|
||||||
|
{
|
||||||
|
for (PopNotification* notification : m_pop_notifications) {
|
||||||
|
if (notification->get_type() == type) {
|
||||||
|
notification->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::compare_warning_oids(const std::vector<size_t>& living_oids)
|
||||||
|
{
|
||||||
|
for (PopNotification* notification : m_pop_notifications) {
|
||||||
|
if (notification->get_type() == NotificationType::SlicingWarning) {
|
||||||
|
auto w = dynamic_cast<SlicingWarningNotification*>(notification);
|
||||||
|
bool found = false;
|
||||||
|
for (size_t oid : living_oids) {
|
||||||
|
if (w->get_object_id() == oid) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found)
|
||||||
|
notification->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool NotificationManager::push_notification_data(const NotificationData ¬ification_data, GLCanvas3D& canvas, int timestamp)
|
||||||
|
{
|
||||||
|
PopNotification* n = new PopNotification(notification_data, m_next_id++, m_evt_handler);
|
||||||
|
bool r = push_notification_data(n, canvas, timestamp);
|
||||||
|
if (!r)
|
||||||
|
delete n;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
bool NotificationManager::push_notification_data(NotificationManager::PopNotification* notification, GLCanvas3D& canvas, int timestamp)
|
||||||
|
{
|
||||||
|
// if timestamped notif, push only new one
|
||||||
|
if (timestamp != 0) {
|
||||||
|
if (m_used_timestamps.find(timestamp) == m_used_timestamps.end()) {
|
||||||
|
m_used_timestamps.insert(timestamp);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!this->find_older(notification)) {
|
||||||
|
m_pop_notifications.emplace_back(notification);
|
||||||
|
canvas.request_extra_frame();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
m_pop_notifications.back()->update(notification->get_data());
|
||||||
|
canvas.request_extra_frame();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::render_notifications(GLCanvas3D& canvas)
|
||||||
|
{
|
||||||
|
float last_x = 0.0f;
|
||||||
|
float current_height = 0.0f;
|
||||||
|
bool request_next_frame = false;
|
||||||
|
bool render_main = false;
|
||||||
|
bool hovered = false;
|
||||||
|
sort_notifications();
|
||||||
|
// iterate thru notifications and render them / erease them
|
||||||
|
for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) {
|
||||||
|
if ((*it)->get_finished()) {
|
||||||
|
delete (*it);
|
||||||
|
it = m_pop_notifications.erase(it);
|
||||||
|
} else {
|
||||||
|
(*it)->set_paused(m_hovered);
|
||||||
|
PopNotification::RenderResult res = (*it)->render(canvas, last_x);
|
||||||
|
if (res != PopNotification::RenderResult::Finished) {
|
||||||
|
last_x = (*it)->get_top() + GAP_WIDTH;
|
||||||
|
current_height = std::max(current_height, (*it)->get_current_top());
|
||||||
|
render_main = true;
|
||||||
|
}
|
||||||
|
if (res == PopNotification::RenderResult::Countdown || res == PopNotification::RenderResult::ClosePending || res == PopNotification::RenderResult::Finished)
|
||||||
|
request_next_frame = true;
|
||||||
|
if (res == PopNotification::RenderResult::Hovered)
|
||||||
|
hovered = true;
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_hovered = hovered;
|
||||||
|
|
||||||
|
//actualizate timers and request frame if needed
|
||||||
|
wxWindow* p = dynamic_cast<wxWindow*> (wxGetApp().plater());
|
||||||
|
while (p->GetParent())
|
||||||
|
p = p->GetParent();
|
||||||
|
wxTopLevelWindow* top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p);
|
||||||
|
if (!top_level_wnd->IsActive())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!m_hovered && m_last_time < wxGetLocalTime())
|
||||||
|
{
|
||||||
|
if (wxGetLocalTime() - m_last_time == 1)
|
||||||
|
{
|
||||||
|
for(auto notification : m_pop_notifications)
|
||||||
|
{
|
||||||
|
notification->substract_remaining_time();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_last_time = wxGetLocalTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request_next_frame)
|
||||||
|
canvas.request_extra_frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void NotificationManager::sort_notifications()
|
||||||
|
{
|
||||||
|
std::sort(m_pop_notifications.begin(), m_pop_notifications.end(), [](PopNotification* n1, PopNotification* n2) {
|
||||||
|
int n1l = (int)n1->get_data().level;
|
||||||
|
int n2l = (int)n2->get_data().level;
|
||||||
|
if (n1l == n2l && n1->get_is_gray() && !n2->get_is_gray())
|
||||||
|
return true;
|
||||||
|
return (n1l < n2l);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NotificationManager::find_older(NotificationManager::PopNotification* notification)
|
||||||
|
{
|
||||||
|
NotificationType type = notification->get_type();
|
||||||
|
std::string text = notification->get_data().text1;
|
||||||
|
for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end(); ++it) {
|
||||||
|
if((*it)->get_type() == type && !(*it)->get_finished()) {
|
||||||
|
if (type == NotificationType::CustomNotification || type == NotificationType::PlaterWarning) {
|
||||||
|
if (!(*it)->compare_text(text))
|
||||||
|
continue;
|
||||||
|
}else if (type == NotificationType::SlicingWarning) {
|
||||||
|
auto w1 = dynamic_cast<SlicingWarningNotification*>(notification);
|
||||||
|
auto w2 = dynamic_cast<SlicingWarningNotification*>(*it);
|
||||||
|
if (w1 != nullptr && w2 != nullptr) {
|
||||||
|
if (!(*it)->compare_text(text) || w1->get_object_id() != w2->get_object_id()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it != m_pop_notifications.end() - 1)
|
||||||
|
std::rotate(it, it + 1, m_pop_notifications.end());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationManager::dpi_changed()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}//namespace GUI
|
||||||
|
}//namespace Slic3r
|
268
src/slic3r/GUI/NotificationManager.hpp
Normal file
268
src/slic3r/GUI/NotificationManager.hpp
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
#ifndef slic3r_GUI_NotificationManager_hpp_
|
||||||
|
#define slic3r_GUI_NotificationManager_hpp_
|
||||||
|
|
||||||
|
#include "Event.hpp"
|
||||||
|
#include "I18N.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <deque>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace GUI {
|
||||||
|
|
||||||
|
using EjectDriveNotificationClickedEvent = SimpleEvent;
|
||||||
|
wxDECLARE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClickedEvent);
|
||||||
|
using ExportGcodeNotificationClickedEvent = SimpleEvent;
|
||||||
|
wxDECLARE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent);
|
||||||
|
using PresetUpdateAviableClickedEvent = SimpleEvent;
|
||||||
|
wxDECLARE_EVENT(EVT_PRESET_UPDATE_AVIABLE_CLICKED, PresetUpdateAviableClickedEvent);
|
||||||
|
|
||||||
|
class GLCanvas3D;
|
||||||
|
class ImGuiWrapper;
|
||||||
|
|
||||||
|
enum class NotificationType
|
||||||
|
{
|
||||||
|
CustomNotification,
|
||||||
|
SlicingComplete,
|
||||||
|
SlicingNotPossible,
|
||||||
|
ExportToRemovableFinished,
|
||||||
|
Mouse3dDisconnected,
|
||||||
|
Mouse3dConnected,
|
||||||
|
NewPresetsAviable,
|
||||||
|
NewAppAviable,
|
||||||
|
PresetUpdateAviable,
|
||||||
|
LoadingFailed,
|
||||||
|
ValidateError, // currently not used - instead Slicing error is used for both slicing and validate errors
|
||||||
|
SlicingError,
|
||||||
|
SlicingWarning,
|
||||||
|
PlaterError,
|
||||||
|
PlaterWarning,
|
||||||
|
ApplyError
|
||||||
|
|
||||||
|
};
|
||||||
|
class NotificationManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class NotificationLevel : int
|
||||||
|
{
|
||||||
|
ErrorNotification = 4,
|
||||||
|
WarningNotification = 3,
|
||||||
|
ImportantNotification = 2,
|
||||||
|
RegularNotification = 1,
|
||||||
|
};
|
||||||
|
// duration 0 means not disapearing
|
||||||
|
struct NotificationData {
|
||||||
|
NotificationType type;
|
||||||
|
NotificationLevel level;
|
||||||
|
const int duration;
|
||||||
|
const std::string text1;
|
||||||
|
const std::string hypertext = std::string();
|
||||||
|
const std::string text2 = std::string();
|
||||||
|
};
|
||||||
|
|
||||||
|
//Pop notification - shows only once to user.
|
||||||
|
class PopNotification
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class RenderResult
|
||||||
|
{
|
||||||
|
Finished,
|
||||||
|
ClosePending,
|
||||||
|
Static,
|
||||||
|
Countdown,
|
||||||
|
Hovered
|
||||||
|
};
|
||||||
|
PopNotification(const NotificationData &n, const int id, wxEvtHandler* evt_handler);
|
||||||
|
virtual ~PopNotification();
|
||||||
|
RenderResult render(GLCanvas3D& canvas, const float& initial_y);
|
||||||
|
// close will dissapear notification on next render
|
||||||
|
void close() { m_close_pending = true; }
|
||||||
|
// data from newer notification of same type
|
||||||
|
void update(const NotificationData& n);
|
||||||
|
bool get_finished() const { return m_finished; }
|
||||||
|
// returns top after movement
|
||||||
|
float get_top() const { return m_top_y; }
|
||||||
|
//returns top in actual frame
|
||||||
|
float get_current_top() const { return m_top_y; }
|
||||||
|
const NotificationType get_type() const { return m_data.type; }
|
||||||
|
const NotificationData get_data() const { return m_data; }
|
||||||
|
const bool get_is_gray() const { return m_is_gray; }
|
||||||
|
// Call equals one second down
|
||||||
|
void substract_remaining_time() { m_remaining_time--; }
|
||||||
|
void set_gray(bool g) { m_is_gray = g; }
|
||||||
|
void set_paused(bool p) { m_paused = p; }
|
||||||
|
bool compare_text(const std::string& text);
|
||||||
|
protected:
|
||||||
|
// Call after every size change
|
||||||
|
void init();
|
||||||
|
// Calculetes correct size but not se it in imgui!
|
||||||
|
virtual void set_next_window_size(ImGuiWrapper& imgui);
|
||||||
|
virtual void render_text(ImGuiWrapper& imgui,
|
||||||
|
const float win_size_x, const float win_size_y,
|
||||||
|
const float win_pos_x , const float win_pos_y);
|
||||||
|
void render_close_button(ImGuiWrapper& imgui,
|
||||||
|
const float win_size_x, const float win_size_y,
|
||||||
|
const float win_pos_x , const float win_pos_y);
|
||||||
|
void render_countdown(ImGuiWrapper& imgui,
|
||||||
|
const float win_size_x, const float win_size_y,
|
||||||
|
const float win_pos_x , const float win_pos_y);
|
||||||
|
void render_hypertext(ImGuiWrapper& imgui,
|
||||||
|
const float text_x, const float text_y,
|
||||||
|
const std::string text,
|
||||||
|
bool more = false);
|
||||||
|
void render_left_sign(ImGuiWrapper& imgui);
|
||||||
|
void render_minimize_button(ImGuiWrapper& imgui,
|
||||||
|
const float win_pos_x, const float win_pos_y);
|
||||||
|
void on_text_click();
|
||||||
|
|
||||||
|
const NotificationData m_data;
|
||||||
|
|
||||||
|
int m_id;
|
||||||
|
// Main text
|
||||||
|
std::string m_text1;
|
||||||
|
// Clickable text
|
||||||
|
std::string m_hypertext;
|
||||||
|
// Aditional text after hypertext - currently not used
|
||||||
|
std::string m_text2;
|
||||||
|
// Countdown variables
|
||||||
|
long m_remaining_time;
|
||||||
|
bool m_counting_down;
|
||||||
|
long m_last_remaining_time;
|
||||||
|
bool m_paused{ false };
|
||||||
|
int m_countdown_frame{ 0 };
|
||||||
|
bool m_fading_out{ false };
|
||||||
|
// total time left when fading beggins
|
||||||
|
float m_fading_time{ 0.0f };
|
||||||
|
float m_current_fade_opacity{ 1.f };
|
||||||
|
// If hidden the notif is alive but not visible to user
|
||||||
|
bool m_hidden { false };
|
||||||
|
// m_finished = true - does not render, marked to delete
|
||||||
|
bool m_finished { false };
|
||||||
|
// Will go to m_finished next render
|
||||||
|
bool m_close_pending { false };
|
||||||
|
// variables to count positions correctly
|
||||||
|
float m_window_width_offset;
|
||||||
|
float m_left_indentation;
|
||||||
|
// Total size of notification window - varies based on monitor
|
||||||
|
float m_window_height { 56.0f };
|
||||||
|
float m_window_width { 450.0f };
|
||||||
|
//Distance from bottom of notifications to top of this notification
|
||||||
|
float m_top_y { 0.0f };
|
||||||
|
|
||||||
|
// Height of text
|
||||||
|
// Used as basic scaling unit!
|
||||||
|
float m_line_height;
|
||||||
|
std::vector<int> m_endlines;
|
||||||
|
// Gray are f.e. eorrors when its uknown if they are still valid
|
||||||
|
bool m_is_gray { false };
|
||||||
|
//if multiline = true, notification is showing all lines(>2)
|
||||||
|
bool m_multiline { false };
|
||||||
|
int m_lines_count{ 1 };
|
||||||
|
wxEvtHandler* m_evt_handler;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SlicingCompleteLargeNotification : public PopNotification
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SlicingCompleteLargeNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler, bool largeds);
|
||||||
|
void set_large(bool l);
|
||||||
|
bool get_large() { return m_is_large; }
|
||||||
|
|
||||||
|
void set_print_info(std::string info);
|
||||||
|
protected:
|
||||||
|
virtual void render_text(ImGuiWrapper& imgui,
|
||||||
|
const float win_size_x, const float win_size_y,
|
||||||
|
const float win_pos_x, const float win_pos_y)
|
||||||
|
override;
|
||||||
|
|
||||||
|
bool m_is_large;
|
||||||
|
bool m_has_print_info { false };
|
||||||
|
std::string m_print_info { std::string() };
|
||||||
|
};
|
||||||
|
|
||||||
|
class SlicingWarningNotification : public PopNotification
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SlicingWarningNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler) : PopNotification(n, id, evt_handler) {}
|
||||||
|
void set_object_id(size_t id) { object_id = id; }
|
||||||
|
const size_t get_object_id() { return object_id; }
|
||||||
|
void set_warning_step(int ws) { warning_step = ws; }
|
||||||
|
const int get_warning_step() { return warning_step; }
|
||||||
|
protected:
|
||||||
|
size_t object_id;
|
||||||
|
int warning_step;
|
||||||
|
};
|
||||||
|
|
||||||
|
NotificationManager(wxEvtHandler* evt_handler);
|
||||||
|
~NotificationManager();
|
||||||
|
|
||||||
|
|
||||||
|
// only type means one of basic_notification (see below)
|
||||||
|
void push_notification(const NotificationType type, GLCanvas3D& canvas, int timestamp = 0);
|
||||||
|
// only text means Undefined type
|
||||||
|
void push_notification(const std::string& text, GLCanvas3D& canvas, int timestamp = 0);
|
||||||
|
void push_notification(const std::string& text, NotificationLevel level, GLCanvas3D& canvas, int timestamp = 0);
|
||||||
|
// creates Slicing Error notification with custom text
|
||||||
|
void push_slicing_error_notification(const std::string& text, GLCanvas3D& canvas);
|
||||||
|
// creates Slicing Warning notification with custom text
|
||||||
|
void push_slicing_warning_notification(const std::string& text, bool gray, GLCanvas3D& canvas, size_t oid, int warning_step);
|
||||||
|
// marks slicing errors as gray
|
||||||
|
void set_all_slicing_errors_gray(bool g);
|
||||||
|
// marks slicing warings as gray
|
||||||
|
void set_all_slicing_warnings_gray(bool g);
|
||||||
|
void set_slicing_warning_gray(const std::string& text, bool g);
|
||||||
|
// imidietly stops showing slicing errors
|
||||||
|
void close_slicing_errors_and_warnings();
|
||||||
|
void compare_warning_oids(const std::vector<size_t>& living_oids);
|
||||||
|
void push_plater_error_notification(const std::string& text, GLCanvas3D& canvas);
|
||||||
|
void push_plater_warning_notification(const std::string& text, GLCanvas3D& canvas);
|
||||||
|
void close_plater_error_notification();
|
||||||
|
void close_plater_warning_notification(const std::string& text);
|
||||||
|
// creates special notification slicing complete
|
||||||
|
// if large = true prints printing time and export button
|
||||||
|
void push_slicing_complete_notification(GLCanvas3D& canvas, int timestamp, bool large);
|
||||||
|
void set_slicing_complete_print_time(std::string info);
|
||||||
|
void set_slicing_complete_large(bool large);
|
||||||
|
// renders notifications in queue and deletes expired ones
|
||||||
|
void render_notifications(GLCanvas3D& canvas);
|
||||||
|
// finds and closes all notifications of given type
|
||||||
|
void close_notification_of_type(const NotificationType type);
|
||||||
|
void dpi_changed();
|
||||||
|
private:
|
||||||
|
//pushes notification into the queue of notifications that are rendered
|
||||||
|
//can be used to create custom notification
|
||||||
|
bool push_notification_data(const NotificationData& notification_data, GLCanvas3D& canvas, int timestamp);
|
||||||
|
bool push_notification_data(NotificationManager::PopNotification* notification, GLCanvas3D& canvas, int timestamp);
|
||||||
|
//finds older notification of same type and moves it to the end of queue. returns true if found
|
||||||
|
bool find_older(NotificationManager::PopNotification* notification);
|
||||||
|
void sort_notifications();
|
||||||
|
|
||||||
|
wxEvtHandler* m_evt_handler;
|
||||||
|
std::deque<PopNotification*> m_pop_notifications;
|
||||||
|
int m_next_id { 1 };
|
||||||
|
long m_last_time { 0 };
|
||||||
|
bool m_hovered { false };
|
||||||
|
//timestamps used for slining finished - notification could be gone so it needs to be stored here
|
||||||
|
std::unordered_set<int> m_used_timestamps;
|
||||||
|
|
||||||
|
//prepared (basic) notifications
|
||||||
|
const std::vector<NotificationData> basic_notifications = {
|
||||||
|
{NotificationType::SlicingNotPossible, NotificationLevel::RegularNotification, 10, _u8L("Slicing is not possible.")},
|
||||||
|
{NotificationType::ExportToRemovableFinished, NotificationLevel::ImportantNotification, 0, _u8L("Exporting finished."), _u8L("Eject drive.") },
|
||||||
|
{NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") },
|
||||||
|
{NotificationType::Mouse3dConnected, NotificationLevel::RegularNotification, 5, _u8L("3D Mouse connected.") },
|
||||||
|
{NotificationType::NewPresetsAviable, NotificationLevel::ImportantNotification, 20, _u8L("New Presets are available."), _u8L("See here.") },
|
||||||
|
{NotificationType::PresetUpdateAviable, NotificationLevel::ImportantNotification, 20, _u8L("Configuration update is available."), _u8L("See more.")},
|
||||||
|
{NotificationType::NewAppAviable, NotificationLevel::ImportantNotification, 20, _u8L("New version is available."), _u8L("See Releases page.")},
|
||||||
|
//{NotificationType::NewAppAviable, NotificationLevel::ImportantNotification, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") },
|
||||||
|
//{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20, _u8L("Loading of model has Failed") },
|
||||||
|
//{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}//namespace GUI
|
||||||
|
}//namespace Slic3r
|
||||||
|
|
||||||
|
#endif //slic3r_GUI_NotificationManager_hpp_
|
@ -79,8 +79,10 @@
|
|||||||
#include "../Utils/PrintHost.hpp"
|
#include "../Utils/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(); }
|
||||||
|
@ -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
|
||||||
|
@ -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",
|
||||||
|
@ -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");
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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")
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
|
@ -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>();
|
||||||
|
@ -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()) {
|
||||||
|
@ -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;
|
||||||
|
99
tests/sla_print/sla_treebuilder_tests.cpp
Normal file
99
tests/sla_print/sla_treebuilder_tests.cpp
Normal 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()));
|
||||||
|
// }
|
||||||
|
//}
|
Loading…
Reference in New Issue
Block a user