Merge branch 'master' of https://github.com/Prusa-Development/PrusaSlicerPrivate into et_outofbed_optimization
This commit is contained in:
commit
53b812a646
72
resources/icons/sla_view_original.svg
Normal file
72
resources/icons/sla_view_original.svg
Normal file
@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
|
||||
<svg
|
||||
width="800px"
|
||||
height="800px"
|
||||
viewBox="0 0 1024 1024"
|
||||
class="icon"
|
||||
version="1.1"
|
||||
id="svg842"
|
||||
sodipodi:docname="sla_view_original.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs846" />
|
||||
<sodipodi:namedview
|
||||
id="namedview844"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:pageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.69208076"
|
||||
inkscape:cx="399.51985"
|
||||
inkscape:cy="496.32936"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1001"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg842" />
|
||||
<path
|
||||
d="M964.266667 812.8c2.133333-8.533333 2.133333-17.066667 2.133333-23.466667s0-17.066667-2.133333-23.466666l49.066666-36.266667c4.266667-4.266667 6.4-10.666667 4.266667-14.933333l-49.066667-83.2c-2.133333-4.266667-8.533333-6.4-14.933333-4.266667l-55.466667 25.6c-12.8-10.666667-27.733333-19.2-42.666666-25.6l-6.4-61.866667c0-6.4-6.4-10.666667-10.666667-10.666666h-96c-6.4 0-10.666667 4.266667-10.666667 10.666666l-6.4 61.866667c-14.933333 6.4-29.866667 14.933333-42.666666 25.6l-55.466667-25.6c-6.4-2.133333-12.8 0-14.933333 4.266667l-49.066667 83.2c-2.133333 4.266667-2.133333 12.8 4.266667 14.933333l49.066666 36.266667c-2.133333 8.533333-2.133333 17.066667-2.133333 23.466666s0 17.066667 2.133333 23.466667l-49.066666 36.266667c-4.266667 4.266667-6.4 10.666667-4.266667 14.933333l49.066667 83.2c2.133333 4.266667 8.533333 6.4 14.933333 4.266667l55.466667-25.6c12.8 10.666667 27.733333 19.2 42.666666 25.6l6.4 61.866666c0 6.4 6.4 10.666667 10.666667 10.666667h96c6.4 0 10.666667-4.266667 10.666667-10.666667l6.4-61.866666c14.933333-6.4 29.866667-14.933333 42.666666-25.6l55.466667 25.6c6.4 2.133333 12.8 0 14.933333-4.266667l49.066667-83.2c2.133333-4.266667 2.133333-12.8-4.266667-14.933333l-49.066666-36.266667zM789.333333 900.266667c-61.866667 0-110.933333-49.066667-110.933333-110.933334 0-61.866667 49.066667-110.933333 110.933333-110.933333 61.866667 0 110.933333 49.066667 110.933334 110.933333 0 61.866667-49.066667 110.933333-110.933334 110.933334z"
|
||||
fill="#607D8B"
|
||||
id="path838"
|
||||
style="fill:#ffffff;fill-opacity:1" />
|
||||
<path
|
||||
d="M789.333333 661.333333c-70.4 0-128 57.6-128 128s57.6 128 128 128 128-57.6 128-128-57.6-128-128-128z m0 192c-36.266667 0-64-27.733333-64-64s27.733333-64 64-64 64 27.733333 64 64-27.733333 64-64 64z"
|
||||
fill="#455A64"
|
||||
id="path840"
|
||||
style="fill:#646464;fill-opacity:0" />
|
||||
<path
|
||||
d="M 464.75606,385.71647 H 220.52618 c -23.46667,0 -42.66667,19.2 -42.66667,42.66668 v 171.15708 c 0,23.46667 19.2,42.66667 42.66667,42.66667 h 244.22988 c 23.46668,0 42.66668,-19.2 42.66668,-42.66667 V 428.38315 c 0,-23.46668 -19.2,-42.66668 -42.66668,-42.66668 z"
|
||||
style="fill:#f7c0a1;stroke-width:1.28;fill-opacity:1"
|
||||
id="path1447"
|
||||
sodipodi:nodetypes="sssssssss" />
|
||||
<path
|
||||
style="fill:#ed6b21;fill-opacity:1;stroke-width:0;paint-order:stroke fill markers"
|
||||
d="m 158.31475,281.62793 c -9.13538,-2.47451 -17.55246,-9.78885 -21.77597,-18.92305 -2.14973,-4.64921 -2.18374,-5.6297 -2.18374,-62.96031 v -58.23755 l 2.5171,-5.10855 c 3.53239,-7.16911 11.05319,-14.20236 17.94366,-16.78042 l 5.66954,-2.12124 H 400 639.51466 l 5.66953,2.12124 c 6.89048,2.57806 14.41128,9.61131 17.94367,16.78042 l 2.5171,5.10855 v 58.23755 58.23755 l -2.83063,5.76399 c -3.30254,6.725 -10.14711,13.42226 -16.91326,16.54928 l -4.77719,2.2078 -239.08046,0.19191 c -186.73355,0.14989 -240.09819,-0.0838 -243.72867,-1.06717 z"
|
||||
id="path7485"
|
||||
transform="scale(1.28)" />
|
||||
<path
|
||||
style="fill:#ed6b21;fill-opacity:1;stroke-width:0;paint-order:stroke fill markers"
|
||||
d="m 437.21118,499.60629 c -10.08409,-1.70675 -19.55357,-8.84836 -24.15393,-18.21621 l -2.84014,-5.78344 v -75.09578 -75.09579 l 2.84014,-5.78344 c 3.4061,-6.93594 9.17745,-12.3958 16.96062,-16.04525 l 5.74203,-2.69238 h 102.17113 102.17114 l 5.74203,2.69238 c 7.78317,3.64945 13.55451,9.10931 16.96062,16.04525 l 2.84014,5.78344 0.12731,66.92209 c 0.10556,55.49011 -0.0746,65.14125 -1.0544,56.49709 -0.97739,-8.62265 -1.60428,-10.87483 -3.62597,-13.02682 l -2.44427,-2.60181 h -41.24765 -41.24765 l -2.50783,2.50784 c -1.37931,1.37931 -2.5167,3.56322 -2.52753,4.85313 -0.0297,3.54851 -4.90813,49.01817 -5.27775,49.19231 -0.17708,0.0835 -5.14956,2.57971 -11.04995,5.54727 l -10.72797,5.39556 -14.94254,-0.0316 -14.94253,-0.0316 -12.03854,-5.65658 c -16.76712,-7.87841 -21.03033,-7.77771 -25.74014,0.60802 l -2.83433,5.04647 -15.83653,-0.11915 c -8.71009,-0.0655 -17.94207,-0.47549 -20.51551,-0.91106 z"
|
||||
id="path7524"
|
||||
transform="scale(1.28)" />
|
||||
<path
|
||||
style="fill:#ed6b21;fill-opacity:1;stroke-width:0;paint-order:stroke fill markers"
|
||||
d="m 154.98937,680.48972 c -7.22252,-2.82207 -13.15353,-8.19884 -17.12576,-15.5254 l -3.50857,-6.47134 v -58.23755 c 0,-57.52794 0.0269,-58.29576 2.20781,-63.01474 3.12702,-6.76614 9.82428,-13.61071 16.54928,-16.91326 l 5.76399,-2.83062 152.41872,-0.27172 152.41871,-0.27172 -12.21117,20.70595 c -6.71614,11.38827 -12.48085,21.74042 -12.81046,23.00479 -1.33796,5.13249 0.30977,6.86623 21.05574,22.15497 11.25014,8.29079 20.61354,15.37577 20.80755,15.7444 0.194,0.36863 -0.1096,4.69322 -0.67466,9.61021 -0.66837,5.81596 -0.68702,12.03751 -0.0533,17.80415 l 0.97403,8.86416 -19.66637,14.61906 c -10.8165,8.04047 -20.1462,15.51562 -20.73266,16.61144 -1.65622,3.09465 -1.2651,7.94874 0.99788,12.38455 l 2.06417,4.04614 -141.78323,-0.0478 c -136.20291,-0.0459 -141.9764,-0.12328 -146.69167,-1.96569 z"
|
||||
id="path7563"
|
||||
transform="scale(1.28)" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke-width:0;paint-order:stroke fill markers;fill-rule:nonzero;stroke:none;stroke-opacity:1"
|
||||
d="m 604.62085,664.56707 c -24.60818,-5.96525 -40.42276,-29.63578 -36.74461,-54.99766 4.89008,-33.71857 40.85293,-52.20054 71.62153,-36.80764 18.19443,9.1023 28.95735,30.19449 25.97417,50.90177 -4.18305,29.03597 -32.17825,47.85408 -60.85109,40.90353 z"
|
||||
id="path7602"
|
||||
transform="scale(1.28)" />
|
||||
</svg>
|
After Width: | Height: | Size: 6.4 KiB |
56
resources/icons/sla_view_processed.svg
Normal file
56
resources/icons/sla_view_processed.svg
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
|
||||
<svg
|
||||
width="800px"
|
||||
height="800px"
|
||||
viewBox="0 0 1024 1024"
|
||||
class="icon"
|
||||
version="1.1"
|
||||
id="svg842"
|
||||
sodipodi:docname="sla_view_processed.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs846" />
|
||||
<sodipodi:namedview
|
||||
id="namedview844"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:pageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.69208076"
|
||||
inkscape:cx="392.29526"
|
||||
inkscape:cy="502.10903"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1001"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg842" />
|
||||
<path
|
||||
d="M789.333333 661.333333c-70.4 0-128 57.6-128 128s57.6 128 128 128 128-57.6 128-128-57.6-128-128-128z m0 192c-36.266667 0-64-27.733333-64-64s27.733333-64 64-64 64 27.733333 64 64-27.733333 64-64 64z"
|
||||
fill="#455A64"
|
||||
id="path840"
|
||||
style="fill:#646464;fill-opacity:0" />
|
||||
<path
|
||||
d="M 809.35888,294.17114 H 565.129 c -23.46667,0 -42.66667,19.2 -42.66667,42.66668 l 0,356.86333 c 0,23.46667 19.2,42.66667 42.66667,42.66667 h 244.22988 c 23.46668,0 42.66668,-19.2 42.66668,-42.66667 l 0,-356.86333 c 0,-23.46668 -19.2,-42.66668 -42.66668,-42.66668 z"
|
||||
style="fill:#ed6b21;fill-opacity:1;stroke-width:1.28"
|
||||
id="path1447-4"
|
||||
sodipodi:nodetypes="sssssssss" />
|
||||
<path
|
||||
style="fill:#ed6b21;fill-opacity:1;stroke-width:0;paint-order:stroke fill markers"
|
||||
d="m 158.31475,281.62793 c -9.13538,-2.47451 -17.55246,-9.78885 -21.77597,-18.92305 -2.14973,-4.64921 -2.18374,-5.6297 -2.18374,-62.96031 v -58.23755 l 2.5171,-5.10855 c 3.53239,-7.16911 11.05319,-14.20236 17.94366,-16.78042 l 5.66954,-2.12124 H 400 639.51466 l 5.66953,2.12124 c 6.89048,2.57806 14.41128,9.61131 17.94367,16.78042 l 2.5171,5.10855 v 58.23755 58.23755 l -2.83063,5.76399 c -3.30254,6.725 -10.14711,13.42226 -16.91326,16.54928 l -4.77719,2.2078 -239.08046,0.19191 c -186.73355,0.14989 -240.09819,-0.0838 -243.72867,-1.06717 z"
|
||||
id="path7485"
|
||||
transform="scale(1.28)" />
|
||||
<path
|
||||
style="fill:#ed6b21;fill-opacity:1;stroke-width:0;paint-order:stroke fill markers"
|
||||
d="m 201.98898,872.0074 c -11.69328,-3.16737 -22.46714,-12.52973 -27.87324,-24.2215 -2.75165,-5.95099 -2.79518,-7.20602 -2.79518,-80.5892 v -74.54406 l 3.22188,-6.53895 c 4.52146,-9.17646 14.14809,-18.17902 22.96789,-21.47894 l 7.25701,-2.71518 H 511.3461 817.92486 l 7.257,2.71518 c 8.81982,3.29992 18.44644,12.30248 22.9679,21.47894 l 3.22189,6.53895 v 74.54406 74.54406 l -3.62321,7.37791 c -4.22725,8.608 -12.9883,17.18049 -21.64897,21.18308 l -6.1148,2.82598 -306.02299,0.24565 C 274.94273,873.56524 206.636,873.26611 201.98898,872.0074 Z"
|
||||
id="path7485-7" />
|
||||
</svg>
|
After Width: | Height: | Size: 3.1 KiB |
@ -166,6 +166,8 @@ namespace ImGui
|
||||
const wchar_t PauseHoverButton = 0x261B;
|
||||
const wchar_t OpenButton = 0x261C;
|
||||
const wchar_t OpenHoverButton = 0x261D;
|
||||
const wchar_t SlaViewOriginal = 0x261E;
|
||||
const wchar_t SlaViewProcessed = 0x261F;
|
||||
|
||||
const wchar_t LegendTravel = 0x2701;
|
||||
const wchar_t LegendWipe = 0x2702;
|
||||
|
128
src/libslic3r/Algorithm/PathSorting.hpp
Normal file
128
src/libslic3r/Algorithm/PathSorting.hpp
Normal file
@ -0,0 +1,128 @@
|
||||
#ifndef SRC_LIBSLIC3R_PATH_SORTING_HPP_
|
||||
#define SRC_LIBSLIC3R_PATH_SORTING_HPP_
|
||||
|
||||
#include "AABBTreeLines.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "ankerl/unordered_dense.h"
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/Polygon.hpp>
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Algorithm {
|
||||
|
||||
//Sorts the paths such that all paths between begin and last_seed are printed first, in some order. The rest of the paths is sorted
|
||||
// such that the paths that are touching some of the already printed are printed first, sorted secondary by the distance to the last point of the last
|
||||
// printed path.
|
||||
// begin, end, and last_seed are random access iterators. touch_limit_distance is used to check if the paths are touching - if any part of the path gets this close
|
||||
// to the second, then they touch.
|
||||
// convert_to_lines is a lambda that should accept the path as argument and return it as Lines vector, in correct order.
|
||||
template<typename RandomAccessIterator, typename ToLines>
|
||||
void sort_paths(RandomAccessIterator begin, RandomAccessIterator end, Point start, double touch_limit_distance, ToLines convert_to_lines)
|
||||
{
|
||||
size_t paths_count = std::distance(begin, end);
|
||||
if (paths_count <= 1)
|
||||
return;
|
||||
|
||||
auto paths_touch = [touch_limit_distance](const AABBTreeLines::LinesDistancer<Line> &left,
|
||||
const AABBTreeLines::LinesDistancer<Line> &right) {
|
||||
for (const Line &l : left.get_lines()) {
|
||||
if (right.distance_from_lines<false>(l.a) < touch_limit_distance) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (right.distance_from_lines<false>(left.get_lines().back().b) < touch_limit_distance) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const Line &l : right.get_lines()) {
|
||||
if (left.distance_from_lines<false>(l.a) < touch_limit_distance) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (left.distance_from_lines<false>(right.get_lines().back().b) < touch_limit_distance) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
std::vector<AABBTreeLines::LinesDistancer<Line>> distancers(paths_count);
|
||||
for (size_t path_idx = 0; path_idx < paths_count; path_idx++) {
|
||||
distancers[path_idx] = AABBTreeLines::LinesDistancer<Line>{convert_to_lines(*std::next(begin, path_idx))};
|
||||
}
|
||||
|
||||
std::vector<std::unordered_set<size_t>> dependencies(paths_count);
|
||||
for (size_t path_idx = 0; path_idx < paths_count; path_idx++) {
|
||||
for (size_t next_path_idx = path_idx + 1; next_path_idx < paths_count; next_path_idx++) {
|
||||
if (paths_touch(distancers[path_idx], distancers[next_path_idx])) {
|
||||
dependencies[next_path_idx].insert(path_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Point current_point = start;
|
||||
|
||||
std::vector<std::pair<size_t, bool>> correct_order_and_direction(paths_count);
|
||||
size_t unsorted_idx = 0;
|
||||
size_t null_idx = size_t(-1);
|
||||
size_t next_idx = null_idx;
|
||||
bool reverse = false;
|
||||
while (unsorted_idx < paths_count) {
|
||||
next_idx = null_idx;
|
||||
double lines_dist = std::numeric_limits<double>::max();
|
||||
for (size_t path_idx = 0; path_idx < paths_count; path_idx++) {
|
||||
if (!dependencies[path_idx].empty())
|
||||
continue;
|
||||
|
||||
double ldist = distancers[path_idx].distance_from_lines<false>(current_point);
|
||||
if (ldist < lines_dist) {
|
||||
const auto &lines = distancers[path_idx].get_lines();
|
||||
double dist_a = (lines.front().a - current_point).cast<double>().squaredNorm();
|
||||
double dist_b = (lines.back().b - current_point).cast<double>().squaredNorm();
|
||||
next_idx = path_idx;
|
||||
reverse = dist_b < dist_a;
|
||||
lines_dist = ldist;
|
||||
}
|
||||
}
|
||||
|
||||
// we have valid next_idx, sort it, update dependencies, update current point
|
||||
correct_order_and_direction[next_idx] = {unsorted_idx, reverse};
|
||||
unsorted_idx++;
|
||||
current_point = reverse ? distancers[next_idx].get_lines().front().a : distancers[next_idx].get_lines().back().b;
|
||||
|
||||
dependencies[next_idx].insert(null_idx); // prevent it from being selected again
|
||||
for (size_t path_idx = 0; path_idx < paths_count; path_idx++) {
|
||||
dependencies[path_idx].erase(next_idx);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t path_idx = 0; path_idx < paths_count; path_idx++) {
|
||||
if (correct_order_and_direction[path_idx].second) {
|
||||
std::next(begin, path_idx)->reverse();
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < correct_order_and_direction.size() - 1; i++) {
|
||||
bool swapped = false;
|
||||
for (size_t j = 0; j < correct_order_and_direction.size() - i - 1; j++) {
|
||||
if (correct_order_and_direction[j].first > correct_order_and_direction[j + 1].first) {
|
||||
std::swap(correct_order_and_direction[j], correct_order_and_direction[j + 1]);
|
||||
std::iter_swap(std::next(begin, j), std::next(begin, j + 1));
|
||||
swapped = true;
|
||||
}
|
||||
}
|
||||
if (swapped == false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::Algorithm
|
||||
|
||||
#endif /*SRC_LIBSLIC3R_PATH_SORTING_HPP_*/
|
@ -22,8 +22,9 @@ set(SLIC3R_SOURCES
|
||||
AABBTreeLines.hpp
|
||||
AABBMesh.hpp
|
||||
AABBMesh.cpp
|
||||
Algorithm/RegionExpansion.cpp
|
||||
Algorithm/PathSorting.hpp
|
||||
Algorithm/RegionExpansion.hpp
|
||||
Algorithm/RegionExpansion.cpp
|
||||
AnyPtr.hpp
|
||||
BoundingBox.cpp
|
||||
BoundingBox.hpp
|
||||
|
@ -306,47 +306,15 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
|
||||
}
|
||||
}
|
||||
|
||||
// Detect narrow internal solid infill area and use ipEnsuring pattern instead.
|
||||
{
|
||||
std::vector<char> narrow_expolygons;
|
||||
static constexpr const auto narrow_pattern = ipEnsuring;
|
||||
for (size_t surface_fill_id = 0, num_old_fills = surface_fills.size(); surface_fill_id < num_old_fills; ++ surface_fill_id)
|
||||
if (SurfaceFill &fill = surface_fills[surface_fill_id]; fill.surface.surface_type == stInternalSolid) {
|
||||
size_t num_expolygons = fill.expolygons.size();
|
||||
narrow_expolygons.clear();
|
||||
narrow_expolygons.reserve(num_expolygons);
|
||||
// Detect narrow expolygons.
|
||||
int num_narrow = 0;
|
||||
for (const ExPolygon &ex : fill.expolygons) {
|
||||
bool narrow = offset_ex(ex, -scaled<float>(NarrowInfillAreaThresholdMM)).empty();
|
||||
num_narrow += int(narrow);
|
||||
narrow_expolygons.emplace_back(narrow);
|
||||
}
|
||||
if (num_narrow == num_expolygons) {
|
||||
// All expolygons are narrow, change the fill pattern.
|
||||
fill.params.pattern = narrow_pattern;
|
||||
} else if (num_narrow > 0) {
|
||||
// Some expolygons are narrow, split the fills.
|
||||
params = fill.params;
|
||||
params.pattern = narrow_pattern;
|
||||
surface_fills.emplace_back(params);
|
||||
SurfaceFill &old_fill = surface_fills[surface_fill_id];
|
||||
SurfaceFill &new_fill = surface_fills.back();
|
||||
new_fill.region_id = old_fill.region_id;
|
||||
new_fill.surface.surface_type = stInternalSolid;
|
||||
new_fill.surface.thickness = old_fill.surface.thickness;
|
||||
new_fill.expolygons.reserve(num_narrow);
|
||||
for (size_t i = 0; i < narrow_expolygons.size(); ++ i)
|
||||
if (narrow_expolygons[i])
|
||||
new_fill.expolygons.emplace_back(std::move(old_fill.expolygons[i]));
|
||||
old_fill.expolygons.erase(std::remove_if(old_fill.expolygons.begin(), old_fill.expolygons.end(),
|
||||
[&narrow_expolygons, ex_first = old_fill.expolygons.data()](const ExPolygon& ex) { return narrow_expolygons[&ex - ex_first]; }),
|
||||
old_fill.expolygons.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
// Use ipEnsuring pattern for all internal Solids.
|
||||
{
|
||||
for (size_t surface_fill_id = 0; surface_fill_id < surface_fills.size(); ++surface_fill_id)
|
||||
if (SurfaceFill &fill = surface_fills[surface_fill_id]; fill.surface.surface_type == stInternalSolid) {
|
||||
fill.params.pattern = ipEnsuring;
|
||||
}
|
||||
}
|
||||
|
||||
return surface_fills;
|
||||
return surface_fills;
|
||||
}
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
|
@ -147,9 +147,9 @@ protected:
|
||||
|
||||
virtual float _layer_angle(size_t idx) const { return (idx & 1) ? float(M_PI/2.) : 0; }
|
||||
|
||||
virtual std::pair<float, Point> _infill_direction(const Surface *surface) const;
|
||||
|
||||
public:
|
||||
virtual std::pair<float, Point> _infill_direction(const Surface *surface) const;
|
||||
static void connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, const double spacing, const FillParams ¶ms);
|
||||
static void connect_infill(Polylines &&infill_ordered, const Polygons &boundary, const BoundingBox& bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms);
|
||||
static void connect_infill(Polylines &&infill_ordered, const std::vector<const Polygon*> &boundary, const BoundingBox &bbox, Polylines &polylines_out, double spacing, const FillParams ¶ms);
|
||||
|
@ -2,81 +2,470 @@
|
||||
#include "../ShortestPath.hpp"
|
||||
#include "../Arachne/WallToolPaths.hpp"
|
||||
|
||||
#include "AABBTreeLines.hpp"
|
||||
#include "Algorithm/PathSorting.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "FillEnsuring.hpp"
|
||||
#include "KDTreeIndirect.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "Polyline.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include "libslic3r.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
ThickPolylines FillEnsuring::fill_surface_arachne(const Surface *surface, const FillParams ¶ms)
|
||||
ThickPolylines make_fill_polylines(
|
||||
const Fill *fill, const Surface *surface, const FillParams ¶ms, bool stop_vibrations, bool fill_gaps, bool connect_extrusions)
|
||||
{
|
||||
assert(params.use_arachne);
|
||||
assert(this->print_config != nullptr && this->print_object_config != nullptr && this->print_region_config != nullptr);
|
||||
assert(fill->print_config != nullptr && fill->print_object_config != nullptr && fill->print_region_config != nullptr);
|
||||
|
||||
const coord_t scaled_spacing = scaled<coord_t>(this->spacing);
|
||||
|
||||
// Perform offset.
|
||||
Slic3r::ExPolygons expp = this->overlap != 0. ? offset_ex(surface->expolygon, scaled<float>(this->overlap)) : ExPolygons{surface->expolygon};
|
||||
// Create the infills for each of the regions.
|
||||
ThickPolylines thick_polylines_out;
|
||||
for (ExPolygon &ex_poly : expp) {
|
||||
Point bbox_size = ex_poly.contour.bounding_box().size();
|
||||
coord_t loops_count = std::max(bbox_size.x(), bbox_size.y()) / scaled_spacing + 1;
|
||||
Polygons polygons = to_polygons(ex_poly);
|
||||
Arachne::WallToolPaths wall_tool_paths(polygons, scaled_spacing, scaled_spacing, loops_count, 0, params.layer_height, *this->print_object_config, *this->print_config);
|
||||
if (std::vector<Arachne::VariableWidthLines> loops = wall_tool_paths.getToolPaths(); !loops.empty()) {
|
||||
std::vector<const Arachne::ExtrusionLine *> all_extrusions;
|
||||
for (Arachne::VariableWidthLines &loop : loops) {
|
||||
if (loop.empty())
|
||||
continue;
|
||||
for (const Arachne::ExtrusionLine &wall : loop)
|
||||
all_extrusions.emplace_back(&wall);
|
||||
auto rotate_thick_polylines = [](ThickPolylines &tpolylines, double cos_angle, double sin_angle) {
|
||||
for (ThickPolyline &tp : tpolylines) {
|
||||
for (auto &p : tp.points) {
|
||||
double px = double(p.x());
|
||||
double py = double(p.y());
|
||||
p.x() = coord_t(round(cos_angle * px - sin_angle * py));
|
||||
p.y() = coord_t(round(cos_angle * py + sin_angle * px));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Split paths using a nearest neighbor search.
|
||||
size_t firts_poly_idx = thick_polylines_out.size();
|
||||
Point last_pos(0, 0);
|
||||
for (const Arachne::ExtrusionLine *extrusion : all_extrusions) {
|
||||
if (extrusion->empty())
|
||||
continue;
|
||||
auto segments_overlap = [](coord_t alow, coord_t ahigh, coord_t blow, coord_t bhigh) {
|
||||
return (alow >= blow && alow <= bhigh) || (ahigh >= blow && ahigh <= bhigh) || (blow >= alow && blow <= ahigh) ||
|
||||
(bhigh >= alow && bhigh <= ahigh);
|
||||
};
|
||||
|
||||
ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion);
|
||||
if (thick_polyline.length() == 0.)
|
||||
//FIXME this should not happen.
|
||||
continue;
|
||||
assert(thick_polyline.size() > 1);
|
||||
assert(thick_polyline.length() > 0.);
|
||||
//assert(thick_polyline.points.size() == thick_polyline.width.size());
|
||||
if (extrusion->is_closed)
|
||||
thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos));
|
||||
const coord_t scaled_spacing = scaled<coord_t>(fill->spacing);
|
||||
double distance_limit_reconnection = 2.0 * double(scaled_spacing);
|
||||
double squared_distance_limit_reconnection = distance_limit_reconnection * distance_limit_reconnection;
|
||||
Polygons filled_area = to_polygons(surface->expolygon);
|
||||
std::pair<float, Point> rotate_vector = fill->_infill_direction(surface);
|
||||
double aligning_angle = -rotate_vector.first + PI;
|
||||
polygons_rotate(filled_area, aligning_angle);
|
||||
BoundingBox bb = get_extents(filled_area);
|
||||
|
||||
assert(thick_polyline.size() > 1);
|
||||
//assert(thick_polyline.points.size() == thick_polyline.width.size());
|
||||
thick_polylines_out.emplace_back(std::move(thick_polyline));
|
||||
last_pos = thick_polylines_out.back().last_point();
|
||||
}
|
||||
Polygons inner_area = stop_vibrations ? intersection(filled_area, opening(filled_area, 2 * scaled_spacing, 3 * scaled_spacing)) :
|
||||
filled_area;
|
||||
|
||||
inner_area = shrink(inner_area, scaled_spacing * 0.5 - scaled<double>(fill->overlap));
|
||||
|
||||
AABBTreeLines::LinesDistancer<Line> area_walls{to_lines(inner_area)};
|
||||
|
||||
// clip the paths to prevent the extruder from getting exactly on the first point of the loop
|
||||
// Keep valid paths only.
|
||||
size_t j = firts_poly_idx;
|
||||
for (size_t i = firts_poly_idx; i < thick_polylines_out.size(); ++i) {
|
||||
assert(thick_polylines_out[i].size() > 1);
|
||||
assert(thick_polylines_out[i].length() > 0.);
|
||||
//assert(thick_polylines_out[i].points.size() == thick_polylines_out[i].width.size());
|
||||
thick_polylines_out[i].clip_end(this->loop_clipping);
|
||||
assert(thick_polylines_out[i].size() > 1);
|
||||
if (thick_polylines_out[i].is_valid()) {
|
||||
if (j < i)
|
||||
thick_polylines_out[j] = std::move(thick_polylines_out[i]);
|
||||
++j;
|
||||
const size_t n_vlines = (bb.max.x() - bb.min.x() + scaled_spacing - 1) / scaled_spacing;
|
||||
std::vector<Line> vertical_lines(n_vlines);
|
||||
coord_t y_min = bb.min.y();
|
||||
coord_t y_max = bb.max.y();
|
||||
for (size_t i = 0; i < n_vlines; i++) {
|
||||
coord_t x = bb.min.x() + i * double(scaled_spacing);
|
||||
vertical_lines[i].a = Point{x, y_min};
|
||||
vertical_lines[i].b = Point{x, y_max};
|
||||
}
|
||||
vertical_lines.push_back(vertical_lines.back());
|
||||
vertical_lines.back().a = Point{coord_t(bb.min.x() + n_vlines * double(scaled_spacing) + scaled_spacing * 0.5), y_min};
|
||||
vertical_lines.back().b = Point{vertical_lines.back().a.x(), y_max};
|
||||
|
||||
std::vector<std::vector<Line>> polygon_sections(n_vlines);
|
||||
|
||||
for (size_t i = 0; i < n_vlines; i++) {
|
||||
const auto intersections = area_walls.intersections_with_line<true>(vertical_lines[i]);
|
||||
|
||||
for (int intersection_idx = 0; intersection_idx < int(intersections.size()) - 1; intersection_idx++) {
|
||||
const auto &a = intersections[intersection_idx];
|
||||
const auto &b = intersections[intersection_idx + 1];
|
||||
if (area_walls.outside((a.first + b.first) / 2) < 0) {
|
||||
if (std::abs(a.first.y() - b.first.y()) > scaled_spacing) {
|
||||
polygon_sections[i].emplace_back(a.first, b.first);
|
||||
}
|
||||
}
|
||||
if (j < thick_polylines_out.size())
|
||||
thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end());
|
||||
}
|
||||
}
|
||||
|
||||
return thick_polylines_out;
|
||||
if (stop_vibrations) {
|
||||
struct Node
|
||||
{
|
||||
int section_idx;
|
||||
int line_idx;
|
||||
int skips_taken = 0;
|
||||
bool neighbours_explored = false;
|
||||
std::vector<std::pair<int, int>> neighbours{};
|
||||
};
|
||||
|
||||
coord_t length_filter = scale_(4);
|
||||
size_t skips_allowed = 2;
|
||||
size_t min_removal_conut = 5;
|
||||
for (int section_idx = 0; section_idx < polygon_sections.size(); section_idx++) {
|
||||
for (int line_idx = 0; line_idx < polygon_sections[section_idx].size(); line_idx++) {
|
||||
if (const Line &line = polygon_sections[section_idx][line_idx]; line.a != line.b && line.length() < length_filter) {
|
||||
std::set<std::pair<int, int>> to_remove{{section_idx, line_idx}};
|
||||
std::vector<Node> to_visit{{section_idx, line_idx}};
|
||||
|
||||
bool initial_touches_long_lines = false;
|
||||
if (section_idx > 0) {
|
||||
for (int prev_line_idx = 0; prev_line_idx < polygon_sections[section_idx - 1].size(); prev_line_idx++) {
|
||||
if (const Line &nl = polygon_sections[section_idx - 1][prev_line_idx];
|
||||
nl.a != nl.b && segments_overlap(line.a.y(), line.b.y(), nl.a.y(), nl.b.y())) {
|
||||
initial_touches_long_lines = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (!to_visit.empty()) {
|
||||
Node curr = to_visit.back();
|
||||
const Line &curr_l = polygon_sections[curr.section_idx][curr.line_idx];
|
||||
if (curr.neighbours_explored) {
|
||||
bool is_valid_for_removal = (curr_l.length() < length_filter) &&
|
||||
((int(to_remove.size()) - curr.skips_taken > min_removal_conut) ||
|
||||
(curr.neighbours.empty() && !initial_touches_long_lines));
|
||||
if (!is_valid_for_removal) {
|
||||
for (const auto &n : curr.neighbours) {
|
||||
if (to_remove.find(n) != to_remove.end()) {
|
||||
is_valid_for_removal = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!is_valid_for_removal) {
|
||||
to_remove.erase({curr.section_idx, curr.line_idx});
|
||||
}
|
||||
to_visit.pop_back();
|
||||
} else {
|
||||
to_visit.back().neighbours_explored = true;
|
||||
int curr_index = to_visit.size() - 1;
|
||||
bool can_use_skip = curr_l.length() <= length_filter && curr.skips_taken < skips_allowed;
|
||||
if (curr.section_idx + 1 < polygon_sections.size()) {
|
||||
for (int lidx = 0; lidx < polygon_sections[curr.section_idx + 1].size(); lidx++) {
|
||||
if (const Line &nl = polygon_sections[curr.section_idx + 1][lidx];
|
||||
nl.a != nl.b && segments_overlap(curr_l.a.y(), curr_l.b.y(), nl.a.y(), nl.b.y()) &&
|
||||
(nl.length() < length_filter || can_use_skip)) {
|
||||
to_visit[curr_index].neighbours.push_back({curr.section_idx + 1, lidx});
|
||||
to_remove.insert({curr.section_idx + 1, lidx});
|
||||
Node next_node{curr.section_idx + 1, lidx, curr.skips_taken + (nl.length() >= length_filter)};
|
||||
to_visit.push_back(next_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &pair : to_remove) {
|
||||
Line &l = polygon_sections[pair.first][pair.second];
|
||||
l.a = l.b;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t section_idx = 0; section_idx < polygon_sections.size(); section_idx++) {
|
||||
polygon_sections[section_idx].erase(std::remove_if(polygon_sections[section_idx].begin(), polygon_sections[section_idx].end(),
|
||||
[](const Line &s) { return s.a == s.b; }),
|
||||
polygon_sections[section_idx].end());
|
||||
std::sort(polygon_sections[section_idx].begin(), polygon_sections[section_idx].end(),
|
||||
[](const Line &a, const Line &b) { return a.a.y() < b.b.y(); });
|
||||
}
|
||||
|
||||
ThickPolylines thick_polylines;
|
||||
{
|
||||
for (const auto &polygon_slice : polygon_sections) {
|
||||
for (const Line &segment : polygon_slice) {
|
||||
ThickPolyline &new_path = thick_polylines.emplace_back();
|
||||
new_path.points.push_back(segment.a);
|
||||
new_path.width.push_back(scaled_spacing);
|
||||
new_path.points.push_back(segment.b);
|
||||
new_path.width.push_back(scaled_spacing);
|
||||
new_path.endpoints = {true, true};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fill_gaps) {
|
||||
Polygons reconstructed_area{};
|
||||
// reconstruct polygon from polygon sections
|
||||
{
|
||||
struct TracedPoly
|
||||
{
|
||||
Points lows;
|
||||
Points highs;
|
||||
};
|
||||
|
||||
std::vector<std::vector<Line>> polygon_sections_w_width = polygon_sections;
|
||||
for (auto &slice : polygon_sections_w_width) {
|
||||
for (Line &l : slice) {
|
||||
l.a -= Point{0.0, 0.5 * scaled_spacing};
|
||||
l.b += Point{0.0, 0.5 * scaled_spacing};
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<TracedPoly> current_traced_polys;
|
||||
for (const auto &polygon_slice : polygon_sections_w_width) {
|
||||
std::unordered_set<const Line *> used_segments;
|
||||
for (TracedPoly &traced_poly : current_traced_polys) {
|
||||
auto candidates_begin = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.lows.back(),
|
||||
[](const Point &low, const Line &seg) { return seg.b.y() > low.y(); });
|
||||
auto candidates_end = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.highs.back(),
|
||||
[](const Point &high, const Line &seg) { return seg.a.y() > high.y(); });
|
||||
|
||||
bool segment_added = false;
|
||||
for (auto candidate = candidates_begin; candidate != candidates_end && !segment_added; candidate++) {
|
||||
if (used_segments.find(&(*candidate)) != used_segments.end()) {
|
||||
continue;
|
||||
}
|
||||
if (connect_extrusions && (traced_poly.lows.back() - candidates_begin->a).cast<double>().squaredNorm() <
|
||||
squared_distance_limit_reconnection) {
|
||||
traced_poly.lows.push_back(candidates_begin->a);
|
||||
} else {
|
||||
traced_poly.lows.push_back(traced_poly.lows.back() + Point{scaled_spacing / 2, 0});
|
||||
traced_poly.lows.push_back(candidates_begin->a - Point{scaled_spacing / 2, 0});
|
||||
traced_poly.lows.push_back(candidates_begin->a);
|
||||
}
|
||||
|
||||
if (connect_extrusions && (traced_poly.highs.back() - candidates_begin->b).cast<double>().squaredNorm() <
|
||||
squared_distance_limit_reconnection) {
|
||||
traced_poly.highs.push_back(candidates_begin->b);
|
||||
} else {
|
||||
traced_poly.highs.push_back(traced_poly.highs.back() + Point{scaled_spacing / 2, 0});
|
||||
traced_poly.highs.push_back(candidates_begin->b - Point{scaled_spacing / 2, 0});
|
||||
traced_poly.highs.push_back(candidates_begin->b);
|
||||
}
|
||||
segment_added = true;
|
||||
used_segments.insert(&(*candidates_begin));
|
||||
}
|
||||
|
||||
if (!segment_added) {
|
||||
// Zero or multiple overlapping segments. Resolving this is nontrivial,
|
||||
// so we just close this polygon and maybe open several new. This will hopefully happen much less often
|
||||
traced_poly.lows.push_back(traced_poly.lows.back() + Point{scaled_spacing / 2, 0});
|
||||
traced_poly.highs.push_back(traced_poly.highs.back() + Point{scaled_spacing / 2, 0});
|
||||
Polygon &new_poly = reconstructed_area.emplace_back(std::move(traced_poly.lows));
|
||||
new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend());
|
||||
traced_poly.lows.clear();
|
||||
traced_poly.highs.clear();
|
||||
}
|
||||
}
|
||||
|
||||
current_traced_polys.erase(std::remove_if(current_traced_polys.begin(), current_traced_polys.end(),
|
||||
[](const TracedPoly &tp) { return tp.lows.empty(); }),
|
||||
current_traced_polys.end());
|
||||
|
||||
for (const auto &segment : polygon_slice) {
|
||||
if (used_segments.find(&segment) == used_segments.end()) {
|
||||
TracedPoly &new_tp = current_traced_polys.emplace_back();
|
||||
new_tp.lows.push_back(segment.a - Point{scaled_spacing / 2, 0});
|
||||
new_tp.lows.push_back(segment.a);
|
||||
new_tp.highs.push_back(segment.b - Point{scaled_spacing / 2, 0});
|
||||
new_tp.highs.push_back(segment.b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add not closed polys
|
||||
for (TracedPoly &traced_poly : current_traced_polys) {
|
||||
Polygon &new_poly = reconstructed_area.emplace_back(std::move(traced_poly.lows));
|
||||
new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend());
|
||||
}
|
||||
}
|
||||
|
||||
reconstructed_area = closing(reconstructed_area, float(SCALED_EPSILON), float(SCALED_EPSILON));
|
||||
ExPolygons gaps_for_additional_filling = diff_ex(filled_area, reconstructed_area);
|
||||
if (fill->overlap != 0) {
|
||||
gaps_for_additional_filling = offset_ex(gaps_for_additional_filling, scaled<float>(fill->overlap));
|
||||
}
|
||||
|
||||
// BoundingBox bbox = get_extents(filled_area);
|
||||
// bbox.offset(scale_(1.));
|
||||
// ::Slic3r::SVG svg(debug_out_path(("surface" + std::to_string(surface->area())).c_str()).c_str(), bbox);
|
||||
// svg.draw(to_lines(filled_area), "red", scale_(0.4));
|
||||
// svg.draw(to_lines(reconstructed_area), "blue", scale_(0.3));
|
||||
// svg.draw(to_lines(gaps_for_additional_filling), "green", scale_(0.2));
|
||||
// svg.draw(vertical_lines, "black", scale_(0.1));
|
||||
// svg.Close();
|
||||
|
||||
for (ExPolygon &ex_poly : gaps_for_additional_filling) {
|
||||
BoundingBox ex_bb = ex_poly.contour.bounding_box();
|
||||
coord_t loops_count = (std::max(ex_bb.size().x(), ex_bb.size().y()) + scaled_spacing - 1) / scaled_spacing;
|
||||
Polygons polygons = to_polygons(ex_poly);
|
||||
Arachne::WallToolPaths wall_tool_paths(polygons, scaled_spacing, scaled_spacing, loops_count, 0, params.layer_height,
|
||||
*fill->print_object_config, *fill->print_config);
|
||||
if (std::vector<Arachne::VariableWidthLines> loops = wall_tool_paths.getToolPaths(); !loops.empty()) {
|
||||
std::vector<const Arachne::ExtrusionLine *> all_extrusions;
|
||||
for (Arachne::VariableWidthLines &loop : loops) {
|
||||
if (loop.empty())
|
||||
continue;
|
||||
for (const Arachne::ExtrusionLine &wall : loop)
|
||||
all_extrusions.emplace_back(&wall);
|
||||
}
|
||||
|
||||
for (const Arachne::ExtrusionLine *extrusion : all_extrusions) {
|
||||
if (extrusion->junctions.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion);
|
||||
if (extrusion->is_closed) {
|
||||
thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, ex_bb.min));
|
||||
thick_polyline.clip_end(scaled_spacing * 0.5);
|
||||
}
|
||||
if (thick_polyline.is_valid() && thick_polyline.length() > 0 && thick_polyline.points.size() > 1) {
|
||||
thick_polylines.push_back(thick_polyline);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(thick_polylines.begin(), thick_polylines.end(), [](const ThickPolyline &left, const ThickPolyline &right) {
|
||||
BoundingBox lbb(left.points);
|
||||
BoundingBox rbb(right.points);
|
||||
if (lbb.min.x() == rbb.min.x())
|
||||
return lbb.min.y() < rbb.min.y();
|
||||
else
|
||||
return lbb.min.x() < rbb.min.x();
|
||||
});
|
||||
|
||||
// connect tiny gap fills to close colinear line
|
||||
struct EndPoint
|
||||
{
|
||||
Vec2d position;
|
||||
size_t polyline_idx;
|
||||
size_t other_end_point_idx;
|
||||
bool is_first;
|
||||
bool used = false;
|
||||
};
|
||||
std::vector<EndPoint> connection_endpoints;
|
||||
connection_endpoints.reserve(thick_polylines.size() * 2);
|
||||
for (size_t pl_idx = 0; pl_idx < thick_polylines.size(); pl_idx++) {
|
||||
size_t current_idx = connection_endpoints.size();
|
||||
connection_endpoints.push_back({thick_polylines[pl_idx].first_point().cast<double>(), pl_idx, current_idx + 1, true});
|
||||
connection_endpoints.push_back({thick_polylines[pl_idx].last_point().cast<double>(), pl_idx, current_idx, false});
|
||||
}
|
||||
|
||||
std::vector<bool> linear_segment_flags(thick_polylines.size());
|
||||
for (size_t i = 0;i < thick_polylines.size(); i++) {
|
||||
const ThickPolyline& tp = thick_polylines[i];
|
||||
linear_segment_flags[i] = tp.points.size() == 2 && tp.points.front().x() == tp.points.back().x() &&
|
||||
tp.width.front() == scaled_spacing && tp.width.back() == scaled_spacing;
|
||||
}
|
||||
|
||||
auto coord_fn = [&connection_endpoints](size_t idx, size_t dim) { return connection_endpoints[idx].position[dim]; };
|
||||
KDTreeIndirect<2, double, decltype(coord_fn)> endpoints_tree{coord_fn, connection_endpoints.size()};
|
||||
for (size_t ep_idx = 0; ep_idx < connection_endpoints.size(); ep_idx++) {
|
||||
EndPoint &ep1 = connection_endpoints[ep_idx];
|
||||
if (!ep1.used) {
|
||||
std::vector<size_t> close_endpoints = find_nearby_points(endpoints_tree, ep1.position, double(scaled_spacing));
|
||||
for (size_t close_endpoint_idx : close_endpoints) {
|
||||
EndPoint &ep2 = connection_endpoints[close_endpoint_idx];
|
||||
if (ep2.used || ep2.polyline_idx == ep1.polyline_idx ||
|
||||
(linear_segment_flags[ep1.polyline_idx] && linear_segment_flags[ep2.polyline_idx])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
EndPoint &target_ep = ep1.polyline_idx > ep2.polyline_idx ? ep1 : ep2;
|
||||
EndPoint &source_ep = ep1.polyline_idx > ep2.polyline_idx ? ep2 : ep1;
|
||||
|
||||
ThickPolyline &target_tp = thick_polylines[target_ep.polyline_idx];
|
||||
ThickPolyline &source_tp = thick_polylines[source_ep.polyline_idx];
|
||||
linear_segment_flags[target_ep.polyline_idx] = linear_segment_flags[ep1.polyline_idx] ||
|
||||
linear_segment_flags[ep2.polyline_idx];
|
||||
|
||||
Vec2d v1 = target_ep.is_first ?
|
||||
(target_tp.points[0] - target_tp.points[1]).cast<double>() :
|
||||
(target_tp.points.back() - target_tp.points[target_tp.points.size() - 1]).cast<double>();
|
||||
Vec2d v2 = source_ep.is_first ?
|
||||
(source_tp.points[1] - source_tp.points[0]).cast<double>() :
|
||||
(source_tp.points[source_tp.points.size() - 1] - source_tp.points.back()).cast<double>();
|
||||
|
||||
if (std::abs(Slic3r::angle(v1, v2)) > PI / 6.0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// connect target_ep and source_ep, result is stored in target_tp, source_tp will be cleared
|
||||
if (target_ep.is_first) {
|
||||
target_tp.reverse();
|
||||
target_ep.is_first = false;
|
||||
connection_endpoints[target_ep.other_end_point_idx].is_first = true;
|
||||
}
|
||||
|
||||
size_t new_start_idx = target_ep.other_end_point_idx;
|
||||
|
||||
if (!source_ep.is_first) {
|
||||
source_tp.reverse();
|
||||
source_ep.is_first = true;
|
||||
connection_endpoints[source_ep.other_end_point_idx].is_first = false;
|
||||
}
|
||||
|
||||
size_t new_end_idx = source_ep.other_end_point_idx;
|
||||
|
||||
target_tp.points.insert(target_tp.points.end(), source_tp.points.begin(), source_tp.points.end());
|
||||
target_tp.width.push_back(target_tp.width.back());
|
||||
target_tp.width.push_back(source_tp.width.front());
|
||||
target_tp.width.insert(target_tp.width.end(), source_tp.width.begin(), source_tp.width.end());
|
||||
target_ep.used = true;
|
||||
source_ep.used = true;
|
||||
|
||||
connection_endpoints[new_start_idx].polyline_idx = target_ep.polyline_idx;
|
||||
connection_endpoints[new_end_idx].polyline_idx = target_ep.polyline_idx;
|
||||
connection_endpoints[new_start_idx].other_end_point_idx = new_end_idx;
|
||||
connection_endpoints[new_end_idx].other_end_point_idx = new_start_idx;
|
||||
source_tp.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thick_polylines.erase(std::remove_if(thick_polylines.begin(), thick_polylines.end(),
|
||||
[scaled_spacing](const ThickPolyline &tp) {
|
||||
return tp.length() < scaled_spacing &&
|
||||
std::all_of(tp.width.begin(), tp.width.end(),
|
||||
[scaled_spacing](double w) { return w < scaled_spacing; });
|
||||
}),
|
||||
thick_polylines.end());
|
||||
}
|
||||
|
||||
Algorithm::sort_paths(thick_polylines.begin(), thick_polylines.end(), bb.min, double(scaled_spacing) * 1.2, [](const ThickPolyline &tp) {
|
||||
Lines ls;
|
||||
Point prev = tp.first_point();
|
||||
for (size_t i = 1; i < tp.points.size(); i++) {
|
||||
ls.emplace_back(prev, tp.points[i]);
|
||||
prev = ls.back().b;
|
||||
}
|
||||
return ls;
|
||||
});
|
||||
|
||||
if (connect_extrusions) {
|
||||
ThickPolylines connected_thick_polylines;
|
||||
if (!thick_polylines.empty()) {
|
||||
connected_thick_polylines.push_back(thick_polylines.front());
|
||||
for (size_t tp_idx = 1; tp_idx < thick_polylines.size(); tp_idx++) {
|
||||
ThickPolyline &tp = thick_polylines[tp_idx];
|
||||
ThickPolyline &tail = connected_thick_polylines.back();
|
||||
Point last = tail.last_point();
|
||||
if ((last - tp.last_point()).cast<double>().squaredNorm() < (last - tp.first_point()).cast<double>().squaredNorm()) {
|
||||
tp.reverse();
|
||||
}
|
||||
if ((last - tp.first_point()).cast<double>().squaredNorm() < squared_distance_limit_reconnection) {
|
||||
tail.points.insert(tail.points.end(), tp.points.begin(), tp.points.end());
|
||||
tail.width.push_back(scaled_spacing);
|
||||
tail.width.push_back(scaled_spacing);
|
||||
tail.width.insert(tail.width.end(), tp.width.begin(), tp.width.end());
|
||||
} else {
|
||||
connected_thick_polylines.push_back(tp);
|
||||
}
|
||||
}
|
||||
}
|
||||
thick_polylines = connected_thick_polylines;
|
||||
}
|
||||
|
||||
rotate_thick_polylines(thick_polylines, cos(-aligning_angle), sin(-aligning_angle));
|
||||
return thick_polylines;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -6,13 +6,19 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class FillEnsuring : public FillRectilinear
|
||||
ThickPolylines make_fill_polylines(
|
||||
const Fill *fill, const Surface *surface, const FillParams ¶ms, bool stop_vibrations, bool fill_gaps, bool connect_extrusions);
|
||||
|
||||
class FillEnsuring : public Fill
|
||||
{
|
||||
public:
|
||||
Fill *clone() const override { return new FillEnsuring(*this); }
|
||||
~FillEnsuring() override = default;
|
||||
Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override { return {}; };
|
||||
ThickPolylines fill_surface_arachne(const Surface *surface, const FillParams ¶ms) override;
|
||||
ThickPolylines fill_surface_arachne(const Surface *surface, const FillParams ¶ms) override
|
||||
{
|
||||
return make_fill_polylines(this, surface, params, true, true, true);
|
||||
};
|
||||
|
||||
protected:
|
||||
void fill_surface_single_arachne(const Surface &surface, const FillParams ¶ms, ThickPolylines &thick_polylines_out);
|
||||
|
@ -438,7 +438,7 @@ ConfigSubstitutions SL1Reader::read(std::vector<ExPolygons> &slices,
|
||||
|
||||
ConfigSubstitutions SL1Reader::read(DynamicPrintConfig &out)
|
||||
{
|
||||
ZipperArchive arch = read_zipper_archive(m_fname, {}, {"png"});
|
||||
ZipperArchive arch = read_zipper_archive(m_fname, {"ini"}, {"png", "thumbnail"});
|
||||
return out.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "SL1.hpp"
|
||||
#include "SL1_SVG.hpp"
|
||||
#include "AnycubicSLA.hpp"
|
||||
#include "I18N.hpp"
|
||||
|
||||
#include "SLAArchiveFormatRegistry.hpp"
|
||||
|
||||
|
@ -83,8 +83,15 @@ ZipperArchive read_zipper_archive(const std::string &zipfname,
|
||||
}))
|
||||
continue;
|
||||
|
||||
if (name == CONFIG_FNAME) { arch.config = read_ini(entry, zip); continue; }
|
||||
if (name == PROFILE_FNAME) { arch.profile = read_ini(entry, zip); continue; }
|
||||
if (name == CONFIG_FNAME) {
|
||||
arch.config = read_ini(entry, zip);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (name == PROFILE_FNAME) {
|
||||
arch.profile = read_ini(entry, zip);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto it = std::lower_bound(
|
||||
arch.entries.begin(), arch.entries.end(),
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "libslic3r/format.hpp"
|
||||
#include "libslic3r/I18N.hpp"
|
||||
#include "libslic3r/GCodeWriter.hpp"
|
||||
#include "libslic3r/I18N.hpp"
|
||||
#include "GCodeProcessor.hpp"
|
||||
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
|
@ -2097,6 +2097,14 @@ bool ModelObject::has_solid_mesh() const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelObject::has_negative_volume_mesh() const
|
||||
{
|
||||
for (const ModelVolume* volume : volumes)
|
||||
if (volume->is_negative_volume())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void ModelVolume::set_material_id(t_model_material_id material_id)
|
||||
{
|
||||
m_material_id = material_id;
|
||||
|
@ -503,6 +503,8 @@ public:
|
||||
|
||||
// Detect if object has at least one solid mash
|
||||
bool has_solid_mesh() const;
|
||||
// Detect if object has at least one negative volume mash
|
||||
bool has_negative_volume_mesh() const;
|
||||
bool is_cut() const { return cut_id.id().valid(); }
|
||||
bool has_connectors() const;
|
||||
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@ -728,7 +729,7 @@ Polylines reconnect_polylines(const Polylines &polylines, double limit_distance)
|
||||
return result;
|
||||
}
|
||||
|
||||
ExtrusionPaths sort_extra_perimeters(ExtrusionPaths extra_perims, int index_of_first_unanchored, double extrusion_spacing)
|
||||
ExtrusionPaths sort_extra_perimeters(const ExtrusionPaths& extra_perims, int index_of_first_unanchored, double extrusion_spacing)
|
||||
{
|
||||
if (extra_perims.empty()) return {};
|
||||
|
||||
|
@ -206,6 +206,9 @@ struct ThickPolyline {
|
||||
void start_at_index(int index);
|
||||
|
||||
Points points;
|
||||
// vector of startpoint width and endpoint width of each line segment. The size should be always (points.size()-1) * 2
|
||||
// e.g. let four be points a,b,c,d. that are three lines ab, bc, cd. for each line, there should be start width, so the width vector is:
|
||||
// w(a), w(b), w(b), w(c), w(c), w(d)
|
||||
std::vector<coordf_t> width;
|
||||
std::pair<bool,bool> endpoints { false, false };
|
||||
};
|
||||
|
@ -403,7 +403,9 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config)
|
||||
is_visible = app_config.get_variant(vendor->id, model, variant);
|
||||
} else if (type == TYPE_FILAMENT || type == TYPE_SLA_MATERIAL) {
|
||||
const std::string §ion_name = (type == TYPE_FILAMENT) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS;
|
||||
if (app_config.has_section(section_name)) {
|
||||
if (type == TYPE_FILAMENT && app_config.get_bool("no_templates") && vendor && vendor->templates_profile)
|
||||
is_visible = false;
|
||||
else if (app_config.has_section(section_name)) {
|
||||
// Check whether this profile is marked as "installed" in PrusaSlicer.ini,
|
||||
// or whether a profile is marked as "installed", which this profile may have been renamed from.
|
||||
const std::map<std::string, std::string> &installed = app_config.get_section(section_name);
|
||||
@ -896,8 +898,9 @@ Preset& PresetCollection::load_preset(const std::string &path, const std::string
|
||||
return preset;
|
||||
}
|
||||
|
||||
void PresetCollection::save_current_preset(const std::string &new_name, bool detach)
|
||||
bool PresetCollection::save_current_preset(const std::string &new_name, bool detach)
|
||||
{
|
||||
bool is_saved_as_new{ false };
|
||||
// 1) Find the preset with a new_name or create a new one,
|
||||
// initialize it with the edited config.
|
||||
auto it = this->find_preset_internal(new_name);
|
||||
@ -906,7 +909,7 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
|
||||
Preset &preset = *it;
|
||||
if (preset.is_default || preset.is_external || preset.is_system)
|
||||
// Cannot overwrite the default preset.
|
||||
return;
|
||||
return false;
|
||||
// Overwriting an existing preset.
|
||||
preset.config = std::move(m_edited_preset.config);
|
||||
// The newly saved preset will be activated -> make it visible.
|
||||
@ -919,6 +922,7 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
|
||||
preset.renamed_from.clear();
|
||||
}
|
||||
} else {
|
||||
is_saved_as_new = true;
|
||||
// Creating a new preset.
|
||||
Preset &preset = *m_presets.insert(it, m_edited_preset);
|
||||
std::string &inherits = preset.inherits();
|
||||
@ -953,6 +957,8 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
|
||||
this->select_preset_by_name(new_name, true);
|
||||
// 2) Store the active preset to disk.
|
||||
this->get_selected_preset().save();
|
||||
|
||||
return is_saved_as_new;
|
||||
}
|
||||
|
||||
Preset& PresetCollection::get_preset_with_name(const std::string& new_name, const Preset* initial_preset)
|
||||
@ -1212,7 +1218,13 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil
|
||||
if (selected)
|
||||
preset_selected.is_compatible = preset_edited.is_compatible;
|
||||
if (preset_edited.vendor && preset_edited.vendor->templates_profile) {
|
||||
indices_of_template_presets.push_back(idx_preset);
|
||||
if (preset_selected.is_visible)
|
||||
indices_of_template_presets.push_back(idx_preset);
|
||||
else {
|
||||
preset_selected.is_compatible = false;
|
||||
if (selected)
|
||||
m_idx_selected = size_t(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
// filter out template profiles where profile with same alias and compability exists
|
||||
@ -2092,6 +2104,136 @@ bool PhysicalPrinterCollection::is_selected(PhysicalPrinterCollection::ConstIter
|
||||
m_selected_preset == preset_name;
|
||||
}
|
||||
|
||||
ExtruderFilaments::ExtruderFilaments(PresetCollection* filaments_collection, size_t extruder_id, std::string selected_name/* = std::string()*/)
|
||||
: m_filaments (filaments_collection)
|
||||
, m_extruder_id(extruder_id)
|
||||
{
|
||||
const std::deque<Preset>& presets = m_filaments->get_presets();
|
||||
for (size_t id = 0; id < presets.size(); id ++)
|
||||
m_extr_filaments.emplace_back(&(presets[id]));
|
||||
|
||||
select_filament(selected_name.empty() ? m_filaments->get_selected_preset_name() : selected_name);
|
||||
}
|
||||
|
||||
const std::string& ExtruderFilaments::get_preset_name_by_alias(const std::string& alias) const
|
||||
{
|
||||
const auto& aliases_map = m_filaments->map_alias_to_profile_name();
|
||||
for (
|
||||
// Find the 1st profile name with the alias.
|
||||
auto it = Slic3r::lower_bound_by_predicate(aliases_map.begin(), aliases_map.end(), [&alias](auto& l) { return l.first < alias; });
|
||||
// Continue over all profile names with the same alias.
|
||||
it != aliases_map.end() && it->first == alias; ++it)
|
||||
if (auto it_filament = find_filament_internal(it->second);
|
||||
it_filament != m_extr_filaments.end() && it_filament->preset->name == it->second &&
|
||||
it_filament->preset->is_visible && (it_filament->is_compatible || size_t(it_filament - m_extr_filaments.begin()) == m_idx_selected))
|
||||
return it_filament->preset->name;
|
||||
return alias;
|
||||
}
|
||||
|
||||
bool ExtruderFilaments::select_filament(const std::string &name_w_suffix, bool force/*= false*/)
|
||||
{
|
||||
std::string name = Preset::remove_suffix_modified(name_w_suffix);
|
||||
// 1) Try to find the preset by its name.
|
||||
auto it = this->find_filament_internal(name);
|
||||
size_t idx = 0;
|
||||
if (it != m_extr_filaments.end() && it->preset->name == name && it->preset->is_visible)
|
||||
// Preset found by its name and it is visible.
|
||||
idx = it - m_extr_filaments.begin();
|
||||
else {
|
||||
// Find the first visible preset.
|
||||
for (size_t i = 0; i < m_extr_filaments.size(); ++i)
|
||||
if (m_extr_filaments[i].preset->is_visible/* && m_extr_filaments[i].is_compatible*/) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
// If the first visible preset was not found, return the 0th element, which is the default preset.
|
||||
}
|
||||
// 2) Select the new preset.
|
||||
if (m_idx_selected != idx || force) {
|
||||
this->select_filament(idx);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t ExtruderFilaments::update_compatible_internal(const PresetWithVendorProfile &active_printer,
|
||||
const PresetWithVendorProfile *active_print,
|
||||
PresetSelectCompatibleType unselect_if_incompatible)
|
||||
{
|
||||
DynamicPrintConfig config;
|
||||
config.set_key_value("printer_preset", new ConfigOptionString(active_printer.preset.name));
|
||||
const ConfigOption* opt = active_printer.preset.config.option("nozzle_diameter");
|
||||
if (opt)
|
||||
config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast<const ConfigOptionFloats*>(opt)->values.size()));
|
||||
bool some_compatible = false;
|
||||
|
||||
// Adjust printer preset config to the first extruder from m_extruder_id
|
||||
Preset printer_preset_adjusted = active_printer.preset;
|
||||
if (m_extruder_id > 0 && !printer_preset_adjusted.config.opt_bool("single_extruder_multi_material")) {
|
||||
DynamicPrintConfig& active_printer_config = printer_preset_adjusted.config;
|
||||
for (const std::string& key : print_config_def.extruder_option_keys()) {
|
||||
if (key == "default_filament_profile")
|
||||
continue;// Ignore this field, because this parameter is not related to the extruder but to whole printer.
|
||||
auto* opt = active_printer_config.option(key, false);
|
||||
if (opt != nullptr && opt->is_vector())
|
||||
static_cast<ConfigOptionVectorBase*>(opt)->set_at(opt, 0, m_extruder_id);
|
||||
}
|
||||
}
|
||||
PresetWithVendorProfile active_printer_adjusted(printer_preset_adjusted, active_printer.vendor);
|
||||
|
||||
std::vector<size_t> indices_of_template_presets;
|
||||
indices_of_template_presets.reserve(m_extr_filaments.size());
|
||||
|
||||
size_t num_default_presets = m_filaments->num_default_presets();
|
||||
for (size_t idx_preset = num_default_presets; idx_preset < m_extr_filaments.size(); ++idx_preset) {
|
||||
const bool is_selected = idx_preset == m_idx_selected;
|
||||
const Preset* preset = m_extr_filaments[idx_preset].preset;
|
||||
Filament& extr_filament = m_extr_filaments[idx_preset];
|
||||
|
||||
const PresetWithVendorProfile this_preset_with_vendor_profile = m_filaments->get_preset_with_vendor_profile(*preset);
|
||||
bool was_compatible = extr_filament.is_compatible;
|
||||
extr_filament.is_compatible = is_compatible_with_printer(this_preset_with_vendor_profile, active_printer_adjusted, &config);
|
||||
some_compatible |= extr_filament.is_compatible;
|
||||
if (active_print != nullptr)
|
||||
extr_filament.is_compatible &= is_compatible_with_print(this_preset_with_vendor_profile, *active_print, active_printer_adjusted);
|
||||
if (!extr_filament.is_compatible && is_selected &&
|
||||
(unselect_if_incompatible == PresetSelectCompatibleType::Always || (unselect_if_incompatible == PresetSelectCompatibleType::OnlyIfWasCompatible && was_compatible)))
|
||||
m_idx_selected = size_t(-1);
|
||||
if (preset->vendor && preset->vendor->templates_profile) {
|
||||
if (preset->is_visible)
|
||||
indices_of_template_presets.push_back(idx_preset);
|
||||
else {
|
||||
extr_filament.is_compatible = false;
|
||||
if (is_selected)
|
||||
m_idx_selected = size_t(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// filter out template profiles where profile with same alias and compability exists
|
||||
if (!indices_of_template_presets.empty()) {
|
||||
for (size_t idx = num_default_presets; idx < m_extr_filaments.size(); ++idx) {
|
||||
const Filament& filament = m_extr_filaments[idx];
|
||||
const VendorProfile* vendor = filament.preset->vendor;
|
||||
if (vendor && !vendor->templates_profile && filament.is_compatible) {
|
||||
const std::string& preset_alias = filament.preset->alias;
|
||||
for (const auto& template_idx : indices_of_template_presets) {
|
||||
if (m_extr_filaments[template_idx].preset->alias == preset_alias) {
|
||||
m_extr_filaments[template_idx].is_compatible = false;
|
||||
// unselect selected template filament if there is non-template alias compatible
|
||||
if (template_idx == m_idx_selected && (unselect_if_incompatible != PresetSelectCompatibleType::Never))
|
||||
m_idx_selected = size_t(-1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m_idx_selected;
|
||||
}
|
||||
|
||||
|
||||
namespace PresetUtils {
|
||||
const VendorProfile::PrinterModel* system_printer_model(const Preset &preset)
|
||||
|
@ -341,7 +341,8 @@ public:
|
||||
// Save the preset under a new name. If the name is different from the old one,
|
||||
// a new preset is stored into the list of presets.
|
||||
// All presets are marked as not modified and the new preset is activated.
|
||||
void save_current_preset(const std::string &new_name, bool detach = false);
|
||||
// return true, if new preset is stored
|
||||
bool save_current_preset(const std::string &new_name, bool detach = false);
|
||||
|
||||
// Find the preset with a new_name or create a new one,
|
||||
// initialize it with the initial_preset config.
|
||||
@ -507,7 +508,7 @@ public:
|
||||
// Generate a file path from a profile name. Add the ".ini" suffix if it is missing.
|
||||
std::string path_from_name(const std::string &new_name) const;
|
||||
|
||||
size_t num_default_presets() { return m_num_default_presets; }
|
||||
size_t num_default_presets() const { return m_num_default_presets; }
|
||||
|
||||
protected:
|
||||
PresetCollection() = default;
|
||||
@ -566,6 +567,8 @@ public:
|
||||
static bool is_dirty(const Preset *edited, const Preset *reference);
|
||||
static std::vector<std::string> dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare = false);
|
||||
static bool is_independent_from_extruder_number_option(const std::string& opt_key);
|
||||
|
||||
const std::vector<std::pair<std::string, std::string>>& map_alias_to_profile_name() { return m_map_alias_to_profile_name; }
|
||||
private:
|
||||
// Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER.
|
||||
Preset::Type m_type;
|
||||
@ -827,6 +830,142 @@ private:
|
||||
};
|
||||
|
||||
|
||||
// ---------------------------------
|
||||
// *** ExtruderFilaments ***
|
||||
// ---------------------------------
|
||||
|
||||
class Filament
|
||||
{
|
||||
public:
|
||||
Filament(const Preset* preset) : preset(preset) {}
|
||||
// Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection.
|
||||
bool operator<(const Filament& other) const { return this->preset->name < other.preset->name; }
|
||||
|
||||
const Preset* preset;
|
||||
bool is_compatible{ true };
|
||||
};
|
||||
|
||||
// Collections of filaments for extruder
|
||||
class ExtruderFilaments
|
||||
{
|
||||
PresetCollection* m_filaments{ nullptr };
|
||||
// Selected filament.
|
||||
size_t m_idx_selected{ size_t(-1) };
|
||||
// List of filaments for this extruder
|
||||
std::deque<Filament> m_extr_filaments;
|
||||
|
||||
size_t m_extruder_id;
|
||||
|
||||
std::string m_cached_selected_name{ std::string() };
|
||||
|
||||
public:
|
||||
ExtruderFilaments(PresetCollection* filaments_collection, size_t extruder_id = 0, std::string selected_name = std::string());
|
||||
|
||||
typedef std::deque<Filament>::iterator Iterator;
|
||||
typedef std::deque<Filament>::const_iterator ConstIterator;
|
||||
Iterator begin() { return m_extr_filaments.begin(); }
|
||||
ConstIterator begin() const { return m_extr_filaments.cbegin(); }
|
||||
ConstIterator cbegin() const { return m_extr_filaments.cbegin(); }
|
||||
Iterator end() { return m_extr_filaments.end(); }
|
||||
ConstIterator end() const { return m_extr_filaments.cend(); }
|
||||
ConstIterator cend() const { return m_extr_filaments.cend(); }
|
||||
|
||||
bool empty() const { return m_extr_filaments.empty(); }
|
||||
|
||||
const std::deque<Filament>& operator()() const { return m_extr_filaments; }
|
||||
|
||||
// Return a filament by an index. If the filament is active, a temporary copy is returned.
|
||||
Filament& filament(size_t idx) { return m_extr_filaments[idx]; }
|
||||
const Filament& filament(size_t idx) const { return const_cast<ExtruderFilaments*>(this)->filament(idx); }
|
||||
|
||||
// Select filament by the full filament name, which contains name of filament, separator and name of selected preset
|
||||
// If full_name doesn't contain name of selected preset, then select first preset in the list for this filament
|
||||
bool select_filament(const std::string& name, bool force = false);
|
||||
void select_filament(size_t idx) { m_idx_selected = idx; }
|
||||
|
||||
std::string get_selected_preset_name() const { return m_idx_selected == size_t(-1) ? std::string() : m_extr_filaments[m_idx_selected].preset->name; }
|
||||
const Preset* get_selected_preset() const { return m_idx_selected == size_t(-1) ? nullptr : m_extr_filaments[m_idx_selected].preset; }
|
||||
const Filament* get_selected_filament() const { return m_idx_selected == size_t(-1) ? nullptr : &m_extr_filaments[m_idx_selected]; }
|
||||
size_t get_selected_idx() const { return m_idx_selected; }
|
||||
|
||||
friend class PresetBundle;
|
||||
|
||||
ExtruderFilaments() = default;
|
||||
ExtruderFilaments& operator=(const ExtruderFilaments& other) = default;
|
||||
|
||||
private:
|
||||
// Find a preset position in the sorted list of presets.
|
||||
// The "-- default -- " preset is always the first, so it needs
|
||||
// to be handled differently.
|
||||
// If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name.
|
||||
std::deque<Filament>::iterator find_filament_internal(const std::string& name)
|
||||
{
|
||||
return Slic3r::lower_bound_by_predicate(m_extr_filaments.begin(), m_extr_filaments.end(), [&name](const auto& l) {
|
||||
return l.preset->name < name;
|
||||
});
|
||||
}
|
||||
std::deque<Filament>::const_iterator find_filament_internal(const std::string& name) const
|
||||
{
|
||||
return const_cast<ExtruderFilaments*>(this)->find_filament_internal(name);
|
||||
}
|
||||
|
||||
void cache_selected_name() { m_cached_selected_name = get_selected_preset_name(); }
|
||||
std::string get_cached_selected_name() const { return m_cached_selected_name; }
|
||||
|
||||
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
|
||||
// If one of the prefered_alternates is compatible, select it.
|
||||
template<typename PreferedCondition>
|
||||
size_t first_compatible_idx(PreferedCondition prefered_condition) const
|
||||
{
|
||||
size_t i = m_filaments->is_default_suppressed() ? m_filaments->num_default_presets() : 0;
|
||||
size_t n = m_extr_filaments.size();
|
||||
size_t i_compatible = n;
|
||||
int match_quality = -1;
|
||||
for (; i < n; ++i)
|
||||
// Since we use the filament selection from Wizard, it's needed to control the preset visibility too
|
||||
if (m_extr_filaments[i].is_compatible && m_filaments->preset(i).is_visible) {
|
||||
int this_match_quality = prefered_condition(*(m_extr_filaments[i].preset));
|
||||
if (this_match_quality > match_quality) {
|
||||
if (match_quality == std::numeric_limits<int>::max())
|
||||
// Better match will not be found.
|
||||
return i;
|
||||
// Store the first compatible profile with highest match quality into i_compatible.
|
||||
i_compatible = i;
|
||||
match_quality = this_match_quality;
|
||||
}
|
||||
}
|
||||
return (i_compatible == n) ?
|
||||
// No compatible preset found, return the default preset.
|
||||
0 :
|
||||
// Compatible preset found.
|
||||
i_compatible;
|
||||
}
|
||||
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
|
||||
size_t first_compatible_idx() const { return this->first_compatible_idx([](const /*Filament*/Preset&) -> int { return 0; }); }
|
||||
|
||||
template<typename PreferedCondition>
|
||||
const Preset* first_compatible(PreferedCondition prefered_condition) { return m_extr_filaments[this->first_compatible_idx(prefered_condition)].preset;}
|
||||
const Preset* first_compatible() { return m_extr_filaments[this->first_compatible_idx()].preset; }
|
||||
|
||||
const std::string& get_preset_name_by_alias(const std::string& alias) const;
|
||||
|
||||
size_t update_compatible_internal(const PresetWithVendorProfile& active_printer, const PresetWithVendorProfile* active_print, PresetSelectCompatibleType unselect_if_incompatible);
|
||||
|
||||
// For Print / Filament presets, disable those, which are not compatible with the printer.
|
||||
template<typename PreferedCondition>
|
||||
void update_compatible(const PresetWithVendorProfile& active_printer, const PresetWithVendorProfile* active_print, PresetSelectCompatibleType select_other_if_incompatible, PreferedCondition prefered_condition)
|
||||
{
|
||||
if (this->update_compatible_internal(active_printer, active_print, select_other_if_incompatible) == (size_t)-1)
|
||||
// Find some other compatible preset, or the "-- default --" preset.
|
||||
this->select_filament(this->first_compatible_idx(prefered_condition));
|
||||
}
|
||||
void update_compatible(const PresetWithVendorProfile& active_printer, const PresetWithVendorProfile* active_print, PresetSelectCompatibleType select_other_if_incompatible)
|
||||
{
|
||||
this->update_compatible(active_printer, active_print, select_other_if_incompatible, [](const /*Filament*/Preset&) -> int { return 0; });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_Preset_hpp_ */
|
||||
|
@ -119,7 +119,7 @@ PresetBundle& PresetBundle::operator=(const PresetBundle &rhs)
|
||||
printers = rhs.printers;
|
||||
physical_printers = rhs.physical_printers;
|
||||
|
||||
filament_presets = rhs.filament_presets;
|
||||
extruders_filaments = rhs.extruders_filaments;
|
||||
project_config = rhs.project_config;
|
||||
vendors = rhs.vendors;
|
||||
obsolete_presets = rhs.obsolete_presets;
|
||||
@ -143,8 +143,7 @@ void PresetBundle::reset(bool delete_files)
|
||||
this->filaments .reset(delete_files);
|
||||
this->sla_materials.reset(delete_files);
|
||||
this->printers .reset(delete_files);
|
||||
this->filament_presets.clear();
|
||||
this->filament_presets.emplace_back(this->filaments.get_selected_preset_name());
|
||||
this->extruders_filaments.clear();
|
||||
this->obsolete_presets.prints.clear();
|
||||
this->obsolete_presets.sla_prints.clear();
|
||||
this->obsolete_presets.filaments.clear();
|
||||
@ -426,7 +425,26 @@ void PresetBundle::load_installed_printers(const AppConfig &config)
|
||||
preset.set_visible_from_appconfig(config);
|
||||
}
|
||||
|
||||
PresetCollection& PresetBundle::get_presets(Preset::Type type)
|
||||
void PresetBundle::cache_extruder_filaments_names()
|
||||
{
|
||||
for (ExtruderFilaments& extr_filaments : extruders_filaments)
|
||||
extr_filaments.cache_selected_name();
|
||||
}
|
||||
|
||||
void PresetBundle::reset_extruder_filaments()
|
||||
{
|
||||
// save previously cached selected names
|
||||
std::vector<std::string> names;
|
||||
for (const ExtruderFilaments& extr_filaments : extruders_filaments)
|
||||
names.push_back(extr_filaments.get_cached_selected_name());
|
||||
|
||||
// Reset extruder_filaments and set names
|
||||
this->extruders_filaments.clear();
|
||||
for (size_t id = 0; id < names.size(); ++id)
|
||||
this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments, id, names[id]));
|
||||
}
|
||||
|
||||
PresetCollection&PresetBundle::get_presets(Preset::Type type)
|
||||
{
|
||||
assert(type >= Preset::TYPE_PRINT && type <= Preset::TYPE_PRINTER);
|
||||
|
||||
@ -437,12 +455,15 @@ PresetCollection& PresetBundle::get_presets(Preset::Type type)
|
||||
}
|
||||
|
||||
|
||||
const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& preset_type, const std::string& alias)
|
||||
const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& preset_type, const std::string& alias, int extruder_id /*= -1*/)
|
||||
{
|
||||
// there are not aliases for Printers profiles
|
||||
if (preset_type == Preset::TYPE_PRINTER || preset_type == Preset::TYPE_INVALID)
|
||||
return alias;
|
||||
|
||||
if (preset_type == Preset::TYPE_FILAMENT)
|
||||
return extruders_filaments[extruder_id].get_preset_name_by_alias(alias);
|
||||
|
||||
const PresetCollection& presets = get_presets(preset_type);
|
||||
|
||||
return presets.get_preset_name_by_alias(alias);
|
||||
@ -462,8 +483,11 @@ void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset::
|
||||
if (type == Preset::TYPE_PRINTER)
|
||||
copy_bed_model_and_texture_if_needed(presets.get_edited_preset().config);
|
||||
|
||||
if (type == Preset::TYPE_FILAMENT)
|
||||
cache_extruder_filaments_names();
|
||||
// Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini
|
||||
presets.save_current_preset(new_name);
|
||||
if (presets.save_current_preset(new_name) && type == Preset::TYPE_FILAMENT)
|
||||
reset_extruder_filaments();
|
||||
// Mark the print & filament enabled if they are compatible with the currently selected preset.
|
||||
// If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible.
|
||||
update_compatible(PresetSelectCompatibleType::Never);
|
||||
@ -602,35 +626,37 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
|
||||
// Load it even if the current printer technology is SLA.
|
||||
// The possibly excessive filament names will be later removed with this->update_multi_material_filament_presets()
|
||||
// once the FFF technology gets selected.
|
||||
this->filament_presets = { filaments.get_selected_preset_name() };
|
||||
this->extruders_filaments.clear();
|
||||
this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments));
|
||||
for (unsigned int i = 1; i < 1000; ++ i) {
|
||||
char name[64];
|
||||
sprintf(name, "filament_%u", i);
|
||||
if (! config.has("presets", name))
|
||||
break;
|
||||
this->filament_presets.emplace_back(remove_ini_suffix(config.get("presets", name)));
|
||||
this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments, i, remove_ini_suffix(config.get("presets", name))));
|
||||
}
|
||||
|
||||
// ! update MM filaments presets before update compatibility
|
||||
this->update_multi_material_filament_presets();
|
||||
// Update visibility of presets based on their compatibility with the active printer.
|
||||
// Always try to select a compatible print and filament preset to the current printer preset,
|
||||
// as the application may have been closed with an active "external" preset, which does not
|
||||
// exist.
|
||||
this->update_compatible(PresetSelectCompatibleType::Always);
|
||||
this->update_multi_material_filament_presets();
|
||||
|
||||
if (initial_printer != nullptr && (preferred_printer == nullptr || initial_printer == preferred_printer)) {
|
||||
// Don't run the following code, as we want to activate default filament / SLA material profiles when installing and selecting a new printer.
|
||||
// Only run this code if just a filament / SLA material was installed by Config Wizard for an active Printer.
|
||||
auto printer_technology = printers.get_selected_preset().printer_technology();
|
||||
if (printer_technology == ptFFF && ! preferred_selection.filament.empty()) {
|
||||
std::string preferred_preset_name = get_preset_name_by_alias(Preset::Type::TYPE_FILAMENT, preferred_selection.filament);
|
||||
const std::string& preferred_preset_name = get_preset_name_by_alias(Preset::Type::TYPE_FILAMENT, preferred_selection.filament, 0);
|
||||
if (auto it = filaments.find_preset_internal(preferred_preset_name);
|
||||
it != filaments.end() && it->is_visible && it->is_compatible) {
|
||||
filaments.select_preset_by_name_strict(preferred_preset_name);
|
||||
this->filament_presets.front() = filaments.get_selected_preset_name();
|
||||
this->extruders_filaments.front().select_filament(filaments.get_selected_preset_name());
|
||||
}
|
||||
} else if (printer_technology == ptSLA && ! preferred_selection.sla_material.empty()) {
|
||||
std::string preferred_preset_name = get_preset_name_by_alias(Preset::Type::TYPE_SLA_MATERIAL, preferred_selection.sla_material);
|
||||
const std::string& preferred_preset_name = get_preset_name_by_alias(Preset::Type::TYPE_SLA_MATERIAL, preferred_selection.sla_material);
|
||||
if (auto it = sla_materials.find_preset_internal(preferred_preset_name);
|
||||
it != sla_materials.end() && it->is_visible && it->is_compatible)
|
||||
sla_materials.select_preset_by_name_strict(preferred_preset_name);
|
||||
@ -648,15 +674,15 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
|
||||
// Export selections (current print, current filaments, current printer) into config.ini
|
||||
void PresetBundle::export_selections(AppConfig &config)
|
||||
{
|
||||
assert(this->printers.get_edited_preset().printer_technology() != ptFFF || filament_presets.size() >= 1);
|
||||
assert(this->printers.get_edited_preset().printer_technology() != ptFFF || filament_presets.size() > 1 || filaments.get_selected_preset_name() == filament_presets.front());
|
||||
assert(this->printers.get_edited_preset().printer_technology() != ptFFF || extruders_filaments.size() >= 1);
|
||||
assert(this->printers.get_edited_preset().printer_technology() != ptFFF || extruders_filaments.size() > 1 || filaments.get_selected_preset().alias == extruders_filaments.front().get_selected_preset()->alias);
|
||||
config.clear_section("presets");
|
||||
config.set("presets", "print", prints.get_selected_preset_name());
|
||||
config.set("presets", "filament", filament_presets.front());
|
||||
for (unsigned i = 1; i < filament_presets.size(); ++i) {
|
||||
config.set("presets", "filament", extruders_filaments.front().get_selected_preset_name());
|
||||
for (unsigned i = 1; i < extruders_filaments.size(); ++i) {
|
||||
char name[64];
|
||||
sprintf(name, "filament_%u", i);
|
||||
config.set("presets", name, filament_presets[i]);
|
||||
config.set("presets", name, extruders_filaments[i].get_selected_preset_name());
|
||||
}
|
||||
|
||||
config.set("presets", "sla_print", sla_prints.get_selected_preset_name());
|
||||
@ -711,8 +737,8 @@ DynamicPrintConfig PresetBundle::full_fff_config() const
|
||||
// First collect the filament configurations based on the user selection of this->filament_presets.
|
||||
// Here this->filaments.find_preset() and this->filaments.first_visible() return the edited copy of the preset if active.
|
||||
std::vector<const DynamicPrintConfig*> filament_configs;
|
||||
for (const std::string &filament_preset_name : this->filament_presets)
|
||||
filament_configs.emplace_back(&this->filaments.find_preset(filament_preset_name, true)->config);
|
||||
for (const auto& extr_filaments : this->extruders_filaments)
|
||||
filament_configs.emplace_back(&this->filaments.find_preset(extr_filaments.get_selected_preset_name(), true)->config);
|
||||
while (filament_configs.size() < num_extruders)
|
||||
filament_configs.emplace_back(&this->filaments.first_visible().config);
|
||||
for (const DynamicPrintConfig *cfg : filament_configs) {
|
||||
@ -763,7 +789,10 @@ DynamicPrintConfig PresetBundle::full_fff_config() const
|
||||
}
|
||||
|
||||
out.option<ConfigOptionString >("print_settings_id", true)->value = this->prints.get_selected_preset_name();
|
||||
out.option<ConfigOptionStrings>("filament_settings_id", true)->values = this->filament_presets;
|
||||
auto& filament_settings_id = out.option<ConfigOptionStrings>("filament_settings_id", true)->values;
|
||||
filament_settings_id.clear();
|
||||
for (const auto& extr_filaments : this->extruders_filaments)
|
||||
filament_settings_id.emplace_back(extr_filaments.get_selected_preset_name());
|
||||
out.option<ConfigOptionString >("printer_settings_id", true)->value = this->printers.get_selected_preset_name();
|
||||
out.option<ConfigOptionString >("physical_printer_settings_id", true)->value = this->physical_printers.get_selected_printer_name();
|
||||
|
||||
@ -981,6 +1010,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
auto old_filament_profile_names = config.option<ConfigOptionStrings>("filament_settings_id", true);
|
||||
old_filament_profile_names->values.resize(num_extruders, std::string());
|
||||
|
||||
this->extruders_filaments.clear();
|
||||
if (num_extruders <= 1) {
|
||||
// Split the "compatible_printers_condition" and "inherits" from the cummulative vectors to separate filament presets.
|
||||
inherits = inherits_values[1];
|
||||
@ -994,8 +1024,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
loaded= &this->filaments.load_preset(this->filaments.path_from_name(name), name, config);
|
||||
loaded->save();
|
||||
}
|
||||
this->filament_presets.clear();
|
||||
this->filament_presets.emplace_back(loaded->name);
|
||||
this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments));
|
||||
} else {
|
||||
assert(is_external);
|
||||
// Split the filament presets, load each of them separately.
|
||||
@ -1014,7 +1043,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
}
|
||||
}
|
||||
// Load the configs into this->filaments and make them active.
|
||||
this->filament_presets = std::vector<std::string>(configs.size());
|
||||
std::vector<std::string> extr_names = std::vector<std::string>(configs.size());
|
||||
// To avoid incorrect selection of the first filament preset (means a value of Preset->m_idx_selected)
|
||||
// in a case when next added preset take a place of previosly selected preset,
|
||||
// we should add presets from last to first
|
||||
@ -1035,8 +1064,11 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
PresetCollection::LoadAndSelect::Never :
|
||||
PresetCollection::LoadAndSelect::OnlyIfModified);
|
||||
any_modified |= modified;
|
||||
this->filament_presets[i] = loaded->name;
|
||||
extr_names[i] = loaded->name;
|
||||
}
|
||||
// create extruders_filaments only when all filaments are loaded
|
||||
for (size_t id = 0; id < extr_names.size(); ++id)
|
||||
this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments, id, extr_names[id]));
|
||||
}
|
||||
|
||||
// 4) Load the project config values (the per extruder wipe matrix etc).
|
||||
@ -1137,9 +1169,11 @@ ConfigSubstitutions PresetBundle::load_config_file_config_bundle(
|
||||
load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filaments .get_selected_preset_name(), true);
|
||||
load_one(this->sla_materials, tmp_bundle.sla_materials, tmp_bundle.sla_materials.get_selected_preset_name(), true);
|
||||
load_one(this->printers, tmp_bundle.printers, tmp_bundle.printers .get_selected_preset_name(), true);
|
||||
|
||||
this->extruders_filaments.clear();
|
||||
this->update_multi_material_filament_presets();
|
||||
for (size_t i = 1; i < std::min(tmp_bundle.filament_presets.size(), this->filament_presets.size()); ++ i)
|
||||
this->filament_presets[i] = load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filament_presets[i], false);
|
||||
for (size_t i = 1; i < std::min(tmp_bundle.extruders_filaments.size(), this->extruders_filaments.size()); ++i)
|
||||
this->extruders_filaments[i].select_filament(load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.extruders_filaments[i].get_selected_preset_name(), false));
|
||||
|
||||
this->update_compatible(PresetSelectCompatibleType::Never);
|
||||
|
||||
@ -1622,9 +1656,12 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(
|
||||
// Activate the first filament preset.
|
||||
if (! active_filaments.empty() && ! active_filaments.front().empty())
|
||||
filaments.select_preset_by_name(active_filaments.front(), true);
|
||||
|
||||
// Extruder_filaments have to be recreated with new loaded filaments
|
||||
this->extruders_filaments.clear();
|
||||
this->update_multi_material_filament_presets();
|
||||
for (size_t i = 0; i < std::min(this->filament_presets.size(), active_filaments.size()); ++ i)
|
||||
this->filament_presets[i] = filaments.find_preset(active_filaments[i], true)->name;
|
||||
for (size_t i = 0; i < std::min(this->extruders_filaments.size(), active_filaments.size()); ++ i)
|
||||
this->extruders_filaments[i].select_filament(filaments.find_preset(active_filaments[i], true)->name);
|
||||
this->update_compatible(PresetSelectCompatibleType::Never);
|
||||
}
|
||||
|
||||
@ -1640,10 +1677,15 @@ void PresetBundle::update_multi_material_filament_presets()
|
||||
auto *nozzle_diameter = static_cast<const ConfigOptionFloats*>(printers.get_edited_preset().config.option("nozzle_diameter"));
|
||||
size_t num_extruders = nozzle_diameter->values.size();
|
||||
// Verify validity of the current filament presets.
|
||||
for (size_t i = 0; i < std::min(this->filament_presets.size(), num_extruders); ++ i)
|
||||
this->filament_presets[i] = this->filaments.find_preset(this->filament_presets[i], true)->name;
|
||||
// Append the rest of filament presets.
|
||||
this->filament_presets.resize(num_extruders, this->filament_presets.empty() ? this->filaments.first_visible().name : this->filament_presets.back());
|
||||
for (size_t i = 0; i < std::min(this->extruders_filaments.size(), num_extruders); ++i)
|
||||
this->extruders_filaments[i].select_filament(this->filaments.find_preset(this->extruders_filaments[i].get_selected_preset_name(), true)->name);
|
||||
|
||||
if (this->extruders_filaments.size() > num_extruders)
|
||||
this->extruders_filaments.resize(num_extruders);
|
||||
else
|
||||
// Append the rest of filament presets.
|
||||
for (size_t id = extruders_filaments.size(); id < num_extruders; id++)
|
||||
extruders_filaments.emplace_back(ExtruderFilaments(&filaments, id, id == 0 ? filaments.first_visible().name : extruders_filaments[id - 1].get_selected_preset_name()));
|
||||
|
||||
// Now verify if wiping_volumes_matrix has proper size (it is used to deduce number of extruders in wipe tower generator):
|
||||
std::vector<double> old_matrix = this->project_config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values;
|
||||
@ -1673,6 +1715,99 @@ void PresetBundle::update_multi_material_filament_presets()
|
||||
}
|
||||
}
|
||||
|
||||
void PresetBundle::update_filaments_compatible(PresetSelectCompatibleType select_other_filament_if_incompatible, int extruder_idx/* = -1*/)
|
||||
{
|
||||
const Preset& printer_preset = this->printers.get_edited_preset();
|
||||
const PresetWithVendorProfile printer_preset_with_vendor_profile = this->printers.get_preset_with_vendor_profile(printer_preset);
|
||||
const PresetWithVendorProfile print_preset_with_vendor_profile = this->prints.get_edited_preset_with_vendor_profile();
|
||||
const std::vector<std::string>& prefered_filament_profiles = printer_preset.config.option<ConfigOptionStrings>("default_filament_profile")->values;
|
||||
|
||||
class PreferedFilamentsProfileMatch
|
||||
{
|
||||
public:
|
||||
PreferedFilamentsProfileMatch(const Preset* preset, const std::vector<std::string>& prefered_names, int extruder_id = 0) :
|
||||
m_extruder_id(extruder_id),
|
||||
m_prefered_alias(preset ? preset->alias : std::string()),
|
||||
m_prefered_filament_type(preset ? preset->config.opt_string("filament_type", extruder_id) : std::string()),
|
||||
m_prefered_names(prefered_names) {}
|
||||
|
||||
int operator()(const Preset& preset) const
|
||||
{
|
||||
// Don't match any properties of the "-- default --" profile or the external profiles when switching printer profile.
|
||||
if (preset.is_default || preset.is_external)
|
||||
return 0;
|
||||
if (!m_prefered_alias.empty() && m_prefered_alias == preset.alias)
|
||||
// Matching an alias, always take this preset with priority.
|
||||
return std::numeric_limits<int>::max();
|
||||
int match_quality = (std::find(m_prefered_names.begin(), m_prefered_names.end(), preset.name) != m_prefered_names.end()) + 1;
|
||||
if (!m_prefered_filament_type.empty() && m_prefered_filament_type == preset.config.opt_string("filament_type", m_extruder_id))
|
||||
match_quality *= 10;
|
||||
return match_quality;
|
||||
}
|
||||
|
||||
private:
|
||||
int m_extruder_id;
|
||||
const std::string m_prefered_alias;
|
||||
const std::string m_prefered_filament_type;
|
||||
const std::vector<std::string>& m_prefered_names;
|
||||
};
|
||||
|
||||
//! ysFIXME - delete after testing
|
||||
//!// First select a first compatible profile for the preset editor.
|
||||
//!this->filaments.update_compatible(printer_preset_with_vendor_profile, &print_preset_with_vendor_profile, select_other_filament_if_incompatible,
|
||||
//! PreferedFilamentsProfileMatch(this->filaments.get_selected_idx() == size_t(-1) ? nullptr : &this->filaments.get_edited_preset(), prefered_filament_profiles));
|
||||
|
||||
// Update compatible for extruder filaments
|
||||
|
||||
auto update_filament_compatible = [this, select_other_filament_if_incompatible, printer_preset_with_vendor_profile, print_preset_with_vendor_profile, prefered_filament_profiles](int idx)
|
||||
{
|
||||
ExtruderFilaments& extr_filaments = extruders_filaments[idx];
|
||||
|
||||
// Remember whether the filament profiles were compatible before updating the filament compatibility.
|
||||
bool filament_preset_was_compatible = false;
|
||||
const Filament* filament_old = extr_filaments.get_selected_filament();
|
||||
if (select_other_filament_if_incompatible != PresetSelectCompatibleType::Never)
|
||||
filament_preset_was_compatible = filament_old && filament_old->is_compatible;
|
||||
|
||||
extr_filaments.update_compatible(printer_preset_with_vendor_profile, &print_preset_with_vendor_profile, select_other_filament_if_incompatible,
|
||||
PreferedFilamentsProfileMatch(filament_old ? filament_old->preset : nullptr, prefered_filament_profiles, idx));
|
||||
|
||||
const Filament* filament = extr_filaments.get_selected_filament();
|
||||
const bool is_compatible = filament && filament->is_compatible;
|
||||
|
||||
if (is_compatible || select_other_filament_if_incompatible == PresetSelectCompatibleType::Never)
|
||||
return;
|
||||
|
||||
// Verify validity of the current filament presets.
|
||||
if (this->extruders_filaments.size() == 1) {
|
||||
// The compatible profile should have been already selected for the preset editor. Just use it.
|
||||
if (select_other_filament_if_incompatible == PresetSelectCompatibleType::Always || filament_preset_was_compatible)
|
||||
extr_filaments.select_filament(this->filaments.get_edited_preset().name);
|
||||
}
|
||||
else {
|
||||
const std::string filament_name = extr_filaments.get_selected_preset_name();
|
||||
if (!filament || (!is_compatible && (select_other_filament_if_incompatible == PresetSelectCompatibleType::Always || filament_preset_was_compatible))) {
|
||||
// Pick a compatible profile. If there are prefered_filament_profiles, use them.
|
||||
std::string compat_filament_name = extr_filaments.first_compatible(PreferedFilamentsProfileMatch(filament->preset, prefered_filament_profiles, idx))->name;
|
||||
if (filament_name != compat_filament_name)
|
||||
extr_filaments.select_filament(compat_filament_name);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (extruder_idx < 0) {
|
||||
// update compatibility for all extruders
|
||||
const size_t num_extruders = static_cast<const ConfigOptionFloats*>(printer_preset.config.option("nozzle_diameter"))->values.size();
|
||||
for (size_t idx = 0; idx < std::min(this->extruders_filaments.size(), num_extruders); idx++)
|
||||
update_filament_compatible(idx);
|
||||
}
|
||||
else
|
||||
update_filament_compatible(extruder_idx);
|
||||
|
||||
if (this->filaments.get_idx_selected() == size_t(-1))
|
||||
this->filaments.select_preset(extruders_filaments[0].get_selected_idx());
|
||||
}
|
||||
|
||||
void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_print_if_incompatible, PresetSelectCompatibleType select_other_filament_if_incompatible)
|
||||
{
|
||||
const Preset &printer_preset = this->printers.get_edited_preset();
|
||||
@ -1727,99 +1862,18 @@ void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_pri
|
||||
const double m_prefered_layer_height;
|
||||
};
|
||||
|
||||
// Matching by the layer height in addition.
|
||||
class PreferedFilamentProfileMatch : public PreferedProfileMatch
|
||||
{
|
||||
public:
|
||||
PreferedFilamentProfileMatch(const Preset *preset, const std::string &prefered_name) :
|
||||
PreferedProfileMatch(preset ? preset->alias : std::string(), prefered_name),
|
||||
m_prefered_filament_type(preset ? preset->config.opt_string("filament_type", 0) : std::string()) {}
|
||||
|
||||
int operator()(const Preset &preset) const
|
||||
{
|
||||
// Don't match any properties of the "-- default --" profile or the external profiles when switching printer profile.
|
||||
if (preset.is_default || preset.is_external)
|
||||
return 0;
|
||||
int match_quality = PreferedProfileMatch::operator()(preset);
|
||||
if (match_quality < std::numeric_limits<int>::max()) {
|
||||
match_quality += 1;
|
||||
if (! m_prefered_filament_type.empty() && m_prefered_filament_type == preset.config.opt_string("filament_type", 0))
|
||||
match_quality *= 10;
|
||||
}
|
||||
return match_quality;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string m_prefered_filament_type;
|
||||
};
|
||||
|
||||
// Matching by the layer height in addition.
|
||||
class PreferedFilamentsProfileMatch
|
||||
{
|
||||
public:
|
||||
PreferedFilamentsProfileMatch(const Preset *preset, const std::vector<std::string> &prefered_names) :
|
||||
m_prefered_alias(preset ? preset->alias : std::string()),
|
||||
m_prefered_filament_type(preset ? preset->config.opt_string("filament_type", 0) : std::string()),
|
||||
m_prefered_names(prefered_names)
|
||||
{}
|
||||
|
||||
int operator()(const Preset &preset) const
|
||||
{
|
||||
// Don't match any properties of the "-- default --" profile or the external profiles when switching printer profile.
|
||||
if (preset.is_default || preset.is_external)
|
||||
return 0;
|
||||
if (! m_prefered_alias.empty() && m_prefered_alias == preset.alias)
|
||||
// Matching an alias, always take this preset with priority.
|
||||
return std::numeric_limits<int>::max();
|
||||
int match_quality = (std::find(m_prefered_names.begin(), m_prefered_names.end(), preset.name) != m_prefered_names.end()) + 1;
|
||||
if (! m_prefered_filament_type.empty() && m_prefered_filament_type == preset.config.opt_string("filament_type", 0))
|
||||
match_quality *= 10;
|
||||
return match_quality;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string m_prefered_alias;
|
||||
const std::string m_prefered_filament_type;
|
||||
const std::vector<std::string> &m_prefered_names;
|
||||
};
|
||||
|
||||
switch (printer_preset.printer_technology()) {
|
||||
case ptFFF:
|
||||
{
|
||||
assert(printer_preset.config.has("default_print_profile"));
|
||||
assert(printer_preset.config.has("default_filament_profile"));
|
||||
const std::vector<std::string> &prefered_filament_profiles = printer_preset.config.option<ConfigOptionStrings>("default_filament_profile")->values;
|
||||
|
||||
this->prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_print_if_incompatible,
|
||||
PreferedPrintProfileMatch(this->prints.get_selected_idx() == size_t(-1) ? nullptr : &this->prints.get_edited_preset(), printer_preset.config.opt_string("default_print_profile")));
|
||||
const PresetWithVendorProfile print_preset_with_vendor_profile = this->prints.get_edited_preset_with_vendor_profile();
|
||||
// Remember whether the filament profiles were compatible before updating the filament compatibility.
|
||||
std::vector<char> filament_preset_was_compatible(this->filament_presets.size(), false);
|
||||
for (size_t idx = 0; idx < this->filament_presets.size(); ++ idx) {
|
||||
Preset *preset = this->filaments.find_preset(this->filament_presets[idx], false);
|
||||
filament_preset_was_compatible[idx] = preset != nullptr && preset->is_compatible;
|
||||
}
|
||||
// First select a first compatible profile for the preset editor.
|
||||
this->filaments.update_compatible(printer_preset_with_vendor_profile, &print_preset_with_vendor_profile, select_other_filament_if_incompatible,
|
||||
PreferedFilamentsProfileMatch(this->filaments.get_selected_idx() == size_t(-1) ? nullptr : &this->filaments.get_edited_preset(), prefered_filament_profiles));
|
||||
if (select_other_filament_if_incompatible != PresetSelectCompatibleType::Never) {
|
||||
// Verify validity of the current filament presets.
|
||||
const std::string prefered_filament_profile = prefered_filament_profiles.empty() ? std::string() : prefered_filament_profiles.front();
|
||||
if (this->filament_presets.size() == 1) {
|
||||
// The compatible profile should have been already selected for the preset editor. Just use it.
|
||||
if (select_other_filament_if_incompatible == PresetSelectCompatibleType::Always || filament_preset_was_compatible.front())
|
||||
this->filament_presets.front() = this->filaments.get_edited_preset().name;
|
||||
} else {
|
||||
for (size_t idx = 0; idx < this->filament_presets.size(); ++ idx) {
|
||||
std::string &filament_name = this->filament_presets[idx];
|
||||
Preset *preset = this->filaments.find_preset(filament_name, false);
|
||||
if (preset == nullptr || (! preset->is_compatible && (select_other_filament_if_incompatible == PresetSelectCompatibleType::Always || filament_preset_was_compatible[idx])))
|
||||
// Pick a compatible profile. If there are prefered_filament_profiles, use them.
|
||||
filament_name = this->filaments.first_compatible(
|
||||
PreferedFilamentProfileMatch(preset,
|
||||
(idx < prefered_filament_profiles.size()) ? prefered_filament_profiles[idx] : prefered_filament_profile)).name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update compatibility for all currently existent extruder_filaments.
|
||||
update_filaments_compatible(select_other_filament_if_incompatible);
|
||||
|
||||
break;
|
||||
}
|
||||
case ptSLA:
|
||||
@ -1875,13 +1929,13 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst
|
||||
c << "sla_print = " << this->sla_prints.get_selected_preset_name() << std::endl;
|
||||
c << "sla_material = " << this->sla_materials.get_selected_preset_name() << std::endl;
|
||||
c << "printer = " << this->printers.get_selected_preset_name() << std::endl;
|
||||
for (size_t i = 0; i < this->filament_presets.size(); ++ i) {
|
||||
for (size_t i = 0; i < this->extruders_filaments.size(); ++ i) {
|
||||
char suffix[64];
|
||||
if (i > 0)
|
||||
sprintf(suffix, "_%d", (int)i);
|
||||
else
|
||||
suffix[0] = 0;
|
||||
c << "filament" << suffix << " = " << this->filament_presets[i] << std::endl;
|
||||
c << "filament" << suffix << " = " << this->extruders_filaments[i].get_selected_preset_name() << std::endl;
|
||||
}
|
||||
|
||||
if (export_physical_printers && this->physical_printers.get_selected_idx() >= 0)
|
||||
@ -1901,9 +1955,11 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst
|
||||
// an optional "(modified)" suffix will be removed from the filament name.
|
||||
void PresetBundle::set_filament_preset(size_t idx, const std::string &name)
|
||||
{
|
||||
if (idx >= filament_presets.size())
|
||||
filament_presets.resize(idx + 1, filaments.default_preset().name);
|
||||
filament_presets[idx] = Preset::remove_suffix_modified(name);
|
||||
if (idx >= extruders_filaments.size()) {
|
||||
for (size_t id = extruders_filaments.size(); id < idx; id++)
|
||||
extruders_filaments.emplace_back(ExtruderFilaments(&filaments, id, filaments.default_preset().name));
|
||||
}
|
||||
extruders_filaments[idx].select_filament(Preset::remove_suffix_modified(name));
|
||||
}
|
||||
|
||||
void PresetBundle::set_default_suppressed(bool default_suppressed)
|
||||
|
@ -51,9 +51,12 @@ public:
|
||||
const PresetCollection& materials(PrinterTechnology pt) const { return pt == ptFFF ? this->filaments : this->sla_materials; }
|
||||
PrinterPresetCollection printers;
|
||||
PhysicalPrinterCollection physical_printers;
|
||||
// Filament preset names for a multi-extruder or multi-material print.
|
||||
// extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size()
|
||||
std::vector<std::string> filament_presets;
|
||||
|
||||
// Filament presets per extruder for a multi-extruder or multi-material print.
|
||||
// extruders_filaments.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size()
|
||||
std::vector<ExtruderFilaments> extruders_filaments;
|
||||
void cache_extruder_filaments_names();
|
||||
void reset_extruder_filaments();
|
||||
|
||||
PresetCollection& get_presets(Preset::Type preset_type);
|
||||
|
||||
@ -132,6 +135,8 @@ public:
|
||||
// update size and content of filament_presets.
|
||||
void update_multi_material_filament_presets();
|
||||
|
||||
void update_filaments_compatible(PresetSelectCompatibleType select_other_filament_if_incompatible, int extruder_idx = -1);
|
||||
|
||||
// Update the is_compatible flag of all print and filament presets depending on whether they are marked
|
||||
// as compatible with the currently selected printer (and print in case of filament presets).
|
||||
// Also updates the is_visible flag of each preset.
|
||||
@ -145,7 +150,7 @@ public:
|
||||
// If the "vendor" section is missing, enable all models and variants of the particular vendor.
|
||||
void load_installed_printers(const AppConfig &config);
|
||||
|
||||
const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias);
|
||||
const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias, int extruder_id = -1);
|
||||
|
||||
// Save current preset of a provided type under a new name. If the name is different from the old one,
|
||||
// Unselected option would be reverted to the beginning values
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "Support/TreeSupport.hpp"
|
||||
#include "Surface.hpp"
|
||||
#include "Slicing.hpp"
|
||||
#include "SurfaceCollection.hpp"
|
||||
#include "Tesselate.hpp"
|
||||
#include "TriangleMeshSlicer.hpp"
|
||||
#include "Utils.hpp"
|
||||
@ -36,7 +37,9 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <float.h>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <oneapi/tbb/blocked_range.h>
|
||||
@ -1607,7 +1610,7 @@ void PrintObject::discover_vertical_shells()
|
||||
#ifdef DEBUG_BRIDGE_OVER_INFILL
|
||||
template<typename T> void debug_draw(std::string name, const T& a, const T& b, const T& c, const T& d)
|
||||
{
|
||||
std::vector<std::string> colors = {"red", "blue", "orange", "green"};
|
||||
std::vector<std::string> colors = {"red", "green", "blue", "orange"};
|
||||
BoundingBox bbox = get_extents(a);
|
||||
bbox.merge(get_extents(b));
|
||||
bbox.merge(get_extents(c));
|
||||
@ -1664,11 +1667,7 @@ void PrintObject::bridge_over_infill()
|
||||
// unsupported area will serve as a filter for polygons worth bridging.
|
||||
Polygons unsupported_area;
|
||||
Polygons lower_layer_solids;
|
||||
bool contains_only_lightning = true;
|
||||
for (const LayerRegion *region : layer->lower_layer->regions()) {
|
||||
if (region->region().config().fill_pattern.value != ipLightning) {
|
||||
contains_only_lightning = false;
|
||||
}
|
||||
Polygons fill_polys = to_polygons(region->fill_expolygons());
|
||||
// initially consider the whole layer unsupported, but also gather solid layers to later cut off supported parts
|
||||
unsupported_area.insert(unsupported_area.end(), fill_polys.begin(), fill_polys.end());
|
||||
@ -1729,6 +1728,98 @@ void PrintObject::bridge_over_infill()
|
||||
}
|
||||
}
|
||||
|
||||
// LIGHTNING INFILL SECTION - If lightning infill is used somewhere, we check the areas that are going to be bridges, and those that rely on the
|
||||
// lightning infill under them get expanded. This somewhat helps to ensure that most of the extrusions are anchored to the lightning infill at the ends.
|
||||
// It requires modifying this instance of print object in a specific way, so that we do not invalidate the pointers in our surfaces_by_layer structure.
|
||||
bool has_lightning_infill = false;
|
||||
for (size_t i = 0; i < this->num_printing_regions(); i++) {
|
||||
if (this->printing_region(i).config().fill_pattern == ipLightning) {
|
||||
has_lightning_infill = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (has_lightning_infill) {
|
||||
// Prepare backup data for the Layer Region infills. Before modfiyng the layer region, we backup its fill surfaces by moving! them into this map.
|
||||
// then a copy is created, modifiyed and passed to lightning infill generator. After generator is created, we restore the original state of the fills
|
||||
// again by moving the data from this map back to the layer regions. This ensures that pointers to surfaces stay valid.
|
||||
std::map<size_t, std::map<const LayerRegion *, SurfaceCollection>> backup_surfaces;
|
||||
for (size_t lidx = 0; lidx < this->layer_count(); lidx++) {
|
||||
backup_surfaces[lidx] = {};
|
||||
}
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()), [po = this, &backup_surfaces,
|
||||
&surfaces_by_layer](tbb::blocked_range<size_t> r) {
|
||||
PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT);
|
||||
for (size_t lidx = r.begin(); lidx < r.end(); lidx++) {
|
||||
if (surfaces_by_layer.find(lidx) == surfaces_by_layer.end())
|
||||
continue;
|
||||
|
||||
Layer *layer = po->get_layer(lidx);
|
||||
const Layer *lower_layer = layer->lower_layer;
|
||||
if (lower_layer == nullptr)
|
||||
continue;
|
||||
|
||||
Polygons lightning_fill;
|
||||
for (const LayerRegion *region : lower_layer->regions()) {
|
||||
if (region->region().config().fill_pattern == ipLightning) {
|
||||
Polygons lf = to_polygons(region->fill_surfaces().filter_by_type(stInternal));
|
||||
lightning_fill.insert(lightning_fill.end(), lf.begin(), lf.end());
|
||||
}
|
||||
}
|
||||
|
||||
if (lightning_fill.empty())
|
||||
continue;
|
||||
|
||||
for (LayerRegion *region : layer->regions()) {
|
||||
backup_surfaces[lidx][region] = std::move(
|
||||
region->m_fill_surfaces); // Make backup copy by move!! so that pointers in candidate surfaces stay valid
|
||||
// Copy the surfaces back, this will make copy, but we will later discard it anyway
|
||||
region->m_fill_surfaces = backup_surfaces[lidx][region];
|
||||
}
|
||||
|
||||
for (LayerRegion *region : layer->regions()) {
|
||||
ExPolygons sparse_infill = to_expolygons(region->fill_surfaces().filter_by_type(stInternal));
|
||||
ExPolygons solid_infill = to_expolygons(region->fill_surfaces().filter_by_type(stInternalSolid));
|
||||
|
||||
if (sparse_infill.empty()) {
|
||||
break;
|
||||
}
|
||||
for (const auto &surface : surfaces_by_layer[lidx]) {
|
||||
if (surface.region != region)
|
||||
continue;
|
||||
ExPolygons expansion = intersection_ex(sparse_infill, expand(surface.new_polys, scaled<float>(3.0)));
|
||||
solid_infill.insert(solid_infill.end(), expansion.begin(), expansion.end());
|
||||
}
|
||||
|
||||
solid_infill = union_safety_offset_ex(solid_infill);
|
||||
sparse_infill = diff_ex(sparse_infill, solid_infill);
|
||||
|
||||
region->m_fill_surfaces.remove_types({stInternalSolid, stInternal});
|
||||
for (const ExPolygon &ep : solid_infill) {
|
||||
region->m_fill_surfaces.surfaces.emplace_back(stInternalSolid, ep);
|
||||
}
|
||||
for (const ExPolygon &ep : sparse_infill) {
|
||||
region->m_fill_surfaces.surfaces.emplace_back(stInternal, ep);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Use the modified surfaces to generate expanded lightning anchors
|
||||
this->m_lightning_generator = this->prepare_lightning_infill_data();
|
||||
|
||||
// And now restore carefully the original surfaces, again using move to avoid reallocation and preserving the validity of the
|
||||
// pointers in surface candidates
|
||||
for (size_t lidx = 0; lidx < this->layer_count(); lidx++) {
|
||||
Layer *layer = this->get_layer(lidx);
|
||||
for (LayerRegion *region : layer->regions()) {
|
||||
if (backup_surfaces[lidx].find(region) != backup_surfaces[lidx].end()) {
|
||||
region->m_fill_surfaces = std::move(backup_surfaces[lidx][region]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::map<size_t, Polylines> infill_lines;
|
||||
// SECTION to generate infill polylines
|
||||
{
|
||||
@ -1740,7 +1831,6 @@ void PrintObject::bridge_over_infill()
|
||||
}
|
||||
|
||||
this->m_adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_bottom_z);
|
||||
this->m_lightning_generator = this->prepare_lightning_infill_data();
|
||||
|
||||
std::vector<size_t> layers_to_generate_infill;
|
||||
for (const auto &pair : surfaces_by_layer) {
|
||||
@ -2018,6 +2108,8 @@ void PrintObject::bridge_over_infill()
|
||||
polygon_sections[i].erase(std::remove_if(polygon_sections[i].begin(), polygon_sections[i].end(),
|
||||
[](const Line &s) { return s.a == s.b; }),
|
||||
polygon_sections[i].end());
|
||||
std::sort(polygon_sections[i].begin(), polygon_sections[i].end(),
|
||||
[](const Line &a, const Line &b) { return a.a.y() < b.b.y(); });
|
||||
}
|
||||
|
||||
// reconstruct polygon from polygon sections
|
||||
@ -2031,36 +2123,40 @@ void PrintObject::bridge_over_infill()
|
||||
for (const auto &polygon_slice : polygon_sections) {
|
||||
std::unordered_set<const Line *> used_segments;
|
||||
for (TracedPoly &traced_poly : current_traced_polys) {
|
||||
auto maybe_first_overlap = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.lows.back(),
|
||||
[](const Point &low, const Line &seg) { return seg.b.y() > low.y(); });
|
||||
auto candidates_begin = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.lows.back(),
|
||||
[](const Point &low, const Line &seg) { return seg.b.y() > low.y(); });
|
||||
auto candidates_end = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.highs.back(),
|
||||
[](const Point &high, const Line &seg) { return seg.a.y() > high.y(); });
|
||||
|
||||
if (maybe_first_overlap != polygon_slice.end() && // segment exists
|
||||
segments_overlap(traced_poly.lows.back().y(), traced_poly.highs.back().y(), maybe_first_overlap->a.y(),
|
||||
maybe_first_overlap->b.y())) // segment is overlapping
|
||||
{
|
||||
// Overlapping segment. In that case, add it
|
||||
// to the traced polygon and add segment to used segments
|
||||
if ((traced_poly.lows.back() - maybe_first_overlap->a).cast<double>().squaredNorm() <
|
||||
bool segment_added = false;
|
||||
for (auto candidate = candidates_begin; candidate != candidates_end && !segment_added; candidate++) {
|
||||
if (used_segments.find(&(*candidate)) != used_segments.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((traced_poly.lows.back() - candidate->a).cast<double>().squaredNorm() <
|
||||
36.0 * double(bridging_flow.scaled_spacing()) * bridging_flow.scaled_spacing()) {
|
||||
traced_poly.lows.push_back(maybe_first_overlap->a);
|
||||
traced_poly.lows.push_back(candidate->a);
|
||||
} else {
|
||||
traced_poly.lows.push_back(traced_poly.lows.back() + Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.lows.push_back(maybe_first_overlap->a - Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.lows.push_back(maybe_first_overlap->a);
|
||||
traced_poly.lows.push_back(candidate->a - Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.lows.push_back(candidate->a);
|
||||
}
|
||||
|
||||
if ((traced_poly.highs.back() - maybe_first_overlap->b).cast<double>().squaredNorm() <
|
||||
if ((traced_poly.highs.back() - candidate->b).cast<double>().squaredNorm() <
|
||||
36.0 * double(bridging_flow.scaled_spacing()) * bridging_flow.scaled_spacing()) {
|
||||
traced_poly.highs.push_back(maybe_first_overlap->b);
|
||||
traced_poly.highs.push_back(candidate->b);
|
||||
} else {
|
||||
traced_poly.highs.push_back(traced_poly.highs.back() + Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.highs.push_back(maybe_first_overlap->b - Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.highs.push_back(maybe_first_overlap->b);
|
||||
traced_poly.highs.push_back(candidate->b - Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.highs.push_back(candidate->b);
|
||||
}
|
||||
used_segments.insert(&(*maybe_first_overlap));
|
||||
} else {
|
||||
// Zero or multiple overlapping segments. Resolving this is nontrivial,
|
||||
// so we just close this polygon and maybe open several new. This will hopefully happen much less often
|
||||
segment_added = true;
|
||||
used_segments.insert(&(*candidate));
|
||||
}
|
||||
|
||||
if (!segment_added) {
|
||||
// Zero overlapping segments, we just close this polygon
|
||||
traced_poly.lows.push_back(traced_poly.lows.back() + Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.highs.push_back(traced_poly.highs.back() + Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows));
|
||||
@ -2278,43 +2374,73 @@ void PrintObject::bridge_over_infill()
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()), [po = this, &surfaces_by_layer](tbb::blocked_range<size_t> r) {
|
||||
PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT);
|
||||
for (size_t lidx = r.begin(); lidx < r.end(); lidx++) {
|
||||
if (surfaces_by_layer.find(lidx) == surfaces_by_layer.end())
|
||||
if (surfaces_by_layer.find(lidx) == surfaces_by_layer.end() && surfaces_by_layer.find(lidx + 1) == surfaces_by_layer.end())
|
||||
continue;
|
||||
Layer *layer = po->get_layer(lidx);
|
||||
|
||||
Polygons cut_from_infill{};
|
||||
for (const auto &surface : surfaces_by_layer.at(lidx)) {
|
||||
cut_from_infill.insert(cut_from_infill.end(), surface.new_polys.begin(), surface.new_polys.end());
|
||||
if (surfaces_by_layer.find(lidx) != surfaces_by_layer.end()) {
|
||||
for (const auto &surface : surfaces_by_layer.at(lidx)) {
|
||||
cut_from_infill.insert(cut_from_infill.end(), surface.new_polys.begin(), surface.new_polys.end());
|
||||
}
|
||||
}
|
||||
|
||||
Polygons additional_ensuring_areas{};
|
||||
if (surfaces_by_layer.find(lidx + 1) != surfaces_by_layer.end()) {
|
||||
for (const auto &surface : surfaces_by_layer.at(lidx + 1)) {
|
||||
auto additional_area = diff(surface.new_polys,
|
||||
shrink(surface.new_polys, surface.region->flow(frSolidInfill).scaled_spacing()));
|
||||
additional_ensuring_areas.insert(additional_ensuring_areas.end(), additional_area.begin(), additional_area.end());
|
||||
}
|
||||
}
|
||||
|
||||
for (LayerRegion *region : layer->regions()) {
|
||||
Surfaces new_surfaces;
|
||||
|
||||
SurfacesPtr internal_infills = region->m_fill_surfaces.filter_by_type(stInternal);
|
||||
ExPolygons new_internal_infills = diff_ex(internal_infills, cut_from_infill);
|
||||
Polygons near_perimeters = to_polygons(union_safety_offset_ex(to_polygons(region->fill_surfaces().surfaces)));
|
||||
near_perimeters = diff(near_perimeters, shrink(near_perimeters, region->flow(frSolidInfill).scaled_spacing()));
|
||||
ExPolygons additional_ensuring = intersection_ex(additional_ensuring_areas, near_perimeters);
|
||||
|
||||
SurfacesPtr internal_infills = region->m_fill_surfaces.filter_by_type(stInternal);
|
||||
ExPolygons new_internal_infills = diff_ex(internal_infills, cut_from_infill);
|
||||
new_internal_infills = diff_ex(new_internal_infills, additional_ensuring);
|
||||
for (const ExPolygon &ep : new_internal_infills) {
|
||||
new_surfaces.emplace_back(*internal_infills.front(), ep);
|
||||
new_surfaces.emplace_back(stInternal, ep);
|
||||
}
|
||||
|
||||
SurfacesPtr internal_solids = region->m_fill_surfaces.filter_by_type(stInternalSolid);
|
||||
for (const CandidateSurface &cs : surfaces_by_layer.at(lidx)) {
|
||||
for (const Surface *surface : internal_solids) {
|
||||
if (cs.original_surface == surface) {
|
||||
Surface tmp{*surface, {}};
|
||||
tmp.surface_type = stInternalBridge;
|
||||
tmp.bridge_angle = cs.bridge_angle;
|
||||
for (const ExPolygon &ep : union_ex(cs.new_polys)) {
|
||||
new_surfaces.emplace_back(tmp, ep);
|
||||
if (surfaces_by_layer.find(lidx) != surfaces_by_layer.end()) {
|
||||
for (const CandidateSurface &cs : surfaces_by_layer.at(lidx)) {
|
||||
for (const Surface *surface : internal_solids) {
|
||||
if (cs.original_surface == surface) {
|
||||
Surface tmp{*surface, {}};
|
||||
tmp.surface_type = stInternalBridge;
|
||||
tmp.bridge_angle = cs.bridge_angle;
|
||||
for (const ExPolygon &ep : union_ex(cs.new_polys)) {
|
||||
new_surfaces.emplace_back(tmp, ep);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ExPolygons new_internal_solids = diff_ex(internal_solids, cut_from_infill);
|
||||
ExPolygons new_internal_solids = to_expolygons(internal_solids);
|
||||
new_internal_solids.insert(new_internal_solids.end(), additional_ensuring.begin(), additional_ensuring.end());
|
||||
new_internal_solids = diff_ex(new_internal_solids, cut_from_infill);
|
||||
new_internal_solids = union_safety_offset_ex(new_internal_solids);
|
||||
for (const ExPolygon &ep : new_internal_solids) {
|
||||
new_surfaces.emplace_back(*internal_solids.front(), ep);
|
||||
new_surfaces.emplace_back(stInternalSolid, ep);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_BRIDGE_OVER_INFILL
|
||||
debug_draw("Aensuring_" + std::to_string(reinterpret_cast<uint64_t>(®ion)), to_polylines(additional_ensuring),
|
||||
to_polylines(near_perimeters), to_polylines(to_polygons(internal_infills)),
|
||||
to_polylines(to_polygons(internal_solids)));
|
||||
debug_draw("Aensuring_" + std::to_string(reinterpret_cast<uint64_t>(®ion)) + "_new", to_polylines(additional_ensuring),
|
||||
to_polylines(near_perimeters), to_polylines(to_polygons(new_internal_infills)),
|
||||
to_polylines(to_polygons(new_internal_solids)));
|
||||
#endif
|
||||
|
||||
region->m_fill_surfaces.remove_types({stInternalSolid, stInternal});
|
||||
region->m_fill_surfaces.append(new_surfaces);
|
||||
}
|
||||
|
@ -2249,6 +2249,7 @@ static void increase_areas_one_layer(
|
||||
// But as branches connecting with the model that are to small have to be culled, the bottom most point has to be not set.
|
||||
// A point can be set on the top most tip layer (maybe more if it should not move for a few layers).
|
||||
parent.state.result_on_layer_reset();
|
||||
parent.state.to_model_gracious = false;
|
||||
#ifdef TREE_SUPPORTS_TRACK_LOST
|
||||
parent.state.verylost = true;
|
||||
#endif // TREE_SUPPORTS_TRACK_LOST
|
||||
@ -4410,7 +4411,10 @@ static void draw_branches(
|
||||
// Don't propagate further than 1.5 * bottom radius.
|
||||
//LayerIndex layers_propagate_max = 2 * bottom_radius / config.layer_height;
|
||||
LayerIndex layers_propagate_max = 5 * bottom_radius / config.layer_height;
|
||||
LayerIndex layer_bottommost = std::max(0, layer_begin - layers_propagate_max);
|
||||
LayerIndex layer_bottommost = branch.path.front()->state.verylost ?
|
||||
// If the tree bottom is hanging in the air, bring it down to some surface.
|
||||
0 :
|
||||
std::max(0, layer_begin - layers_propagate_max);
|
||||
// Only propagate until the rest area is smaller than this threshold.
|
||||
double support_area_stop = 0.2 * M_PI * sqr(double(bottom_radius));
|
||||
// Only propagate until the rest area is smaller than this threshold.
|
||||
|
@ -91,7 +91,7 @@ struct AreaIncreaseSettings
|
||||
|
||||
struct TreeSupportSettings;
|
||||
|
||||
// #define TREE_SUPPORTS_TRACK_LOST
|
||||
#define TREE_SUPPORTS_TRACK_LOST
|
||||
|
||||
// C++17 does not support in place initializers of bit values, thus a constructor zeroing the bits is provided.
|
||||
struct SupportElementStateBits {
|
||||
|
@ -36,6 +36,8 @@
|
||||
#define ENABLE_MATRICES_DEBUG 0
|
||||
// Shows an imgui dialog containing data from class ObjectManipulation
|
||||
#define ENABLE_OBJECT_MANIPULATION_DEBUG 0
|
||||
// Shows an imgui dialog containing data for class GLCanvas3D::SLAView
|
||||
#define ENABLE_SLA_VIEW_DEBUG_WINDOW 0
|
||||
|
||||
|
||||
// Enable rendering of objects using environment map
|
||||
|
@ -439,6 +439,14 @@ void Bed3D::render_model(const Transform3d& view_matrix, const Transform3d& proj
|
||||
m_model_offset = to_3d(m_build_volume.bounding_volume2d().center(), -0.03);
|
||||
|
||||
// register for picking
|
||||
const std::vector<std::shared_ptr<SceneRaycasterItem>>* const raycaster = wxGetApp().plater()->canvas3D()->get_raycasters_for_picking(SceneRaycaster::EType::Bed);
|
||||
if (!raycaster->empty()) {
|
||||
// The raycaster may have been set by the call to init_triangles() made from render_texture() if the printbed was
|
||||
// changed while the camera was pointing upward.
|
||||
// In this case we need to remove it before creating a new using the model geometry
|
||||
wxGetApp().plater()->canvas3D()->remove_raycasters_for_picking(SceneRaycaster::EType::Bed);
|
||||
m_model.mesh_raycaster.reset();
|
||||
}
|
||||
register_raycasters_for_picking(m_model.model.get_geometry(), Geometry::translation_transform(m_model_offset));
|
||||
|
||||
// update extended bounding box
|
||||
|
@ -611,10 +611,28 @@ void GLVolumeCollection::load_object_auxiliary(
|
||||
if (convex_hull.has_value())
|
||||
v.set_convex_hull(*convex_hull);
|
||||
v.is_modifier = false;
|
||||
v.shader_outside_printer_detection_enabled = (step == slaposSupportTree);
|
||||
v.shader_outside_printer_detection_enabled = (step == slaposSupportTree || step == slaposDrillHoles);
|
||||
v.set_instance_transformation(model_instance.get_transformation());
|
||||
};
|
||||
|
||||
if (milestone == SLAPrintObjectStep::slaposDrillHoles) {
|
||||
if (print_object->get_parts_to_slice().size() > 1) {
|
||||
// Get the mesh.
|
||||
TriangleMesh backend_mesh;
|
||||
std::shared_ptr<const indexed_triangle_set> preview_mesh_ptr = print_object->get_mesh_to_print();
|
||||
if (preview_mesh_ptr != nullptr)
|
||||
backend_mesh = TriangleMesh(*preview_mesh_ptr);
|
||||
if (!backend_mesh.empty()) {
|
||||
backend_mesh.transform(mesh_trafo_inv);
|
||||
TriangleMesh convex_hull = backend_mesh.convex_hull_3d();
|
||||
for (const std::pair<size_t, size_t>& instance_idx : instances) {
|
||||
const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first];
|
||||
add_volume(obj_idx, (int)instance_idx.first, model_instance, slaposDrillHoles, backend_mesh, GLVolume::MODEL_COLOR[0], convex_hull);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the support mesh.
|
||||
if (milestone == SLAPrintObjectStep::slaposSupportTree) {
|
||||
TriangleMesh supports_mesh = print_object->support_mesh();
|
||||
@ -622,8 +640,8 @@ void GLVolumeCollection::load_object_auxiliary(
|
||||
supports_mesh.transform(mesh_trafo_inv);
|
||||
TriangleMesh convex_hull = supports_mesh.convex_hull_3d();
|
||||
for (const std::pair<size_t, size_t>& instance_idx : instances) {
|
||||
const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first];
|
||||
add_volume(obj_idx, (int)instance_idx.first, model_instance, slaposSupportTree, supports_mesh, GLVolume::SLA_SUPPORT_COLOR, convex_hull);
|
||||
const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first];
|
||||
add_volume(obj_idx, (int)instance_idx.first, model_instance, slaposSupportTree, supports_mesh, GLVolume::SLA_SUPPORT_COLOR, convex_hull);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -864,7 +882,7 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con
|
||||
}
|
||||
|
||||
for (GLVolume* volume : volumes) {
|
||||
if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || volume->volume_idx() < 0)
|
||||
if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || volume->is_sla_pad() || volume->is_sla_support())
|
||||
continue;
|
||||
|
||||
int extruder_id = volume->extruder_id - 1;
|
||||
|
@ -3323,6 +3323,8 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
|
||||
: DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(name()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
||||
, p(new priv(this))
|
||||
{
|
||||
wxBusyCursor wait;
|
||||
|
||||
this->SetFont(wxGetApp().normal_font());
|
||||
|
||||
p->load_vendors();
|
||||
|
@ -4066,69 +4066,58 @@ void GCodeViewer::render_legend(float& legend_height)
|
||||
}
|
||||
};
|
||||
|
||||
auto image_icon = [&imgui](ImGuiWindow& window, const ImVec2& pos, float size, const wchar_t& icon_id) {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
const ImTextureID tex_id = io.Fonts->TexID;
|
||||
const float tex_w = static_cast<float>(io.Fonts->TexWidth);
|
||||
const float tex_h = static_cast<float>(io.Fonts->TexHeight);
|
||||
const ImFontAtlas::CustomRect* const rect = imgui.GetTextureCustomRect(icon_id);
|
||||
const ImVec2 uv0 = { static_cast<float>(rect->X) / tex_w, static_cast<float>(rect->Y) / tex_h };
|
||||
const ImVec2 uv1 = { static_cast<float>(rect->X + rect->Width) / tex_w, static_cast<float>(rect->Y + rect->Height) / tex_h };
|
||||
window.DrawList->AddImage(tex_id, pos, { pos.x + size, pos.y + size }, uv0, uv1, ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f }));
|
||||
};
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
toggle_button(Preview::OptionType::Travel, _u8L("Travel"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
image_icon(window, pos, size, ImGui::LegendTravel);
|
||||
toggle_button(Preview::OptionType::Travel, _u8L("Travel"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
imgui.draw_icon(window, pos, size, ImGui::LegendTravel);
|
||||
});
|
||||
ImGui::SameLine();
|
||||
toggle_button(Preview::OptionType::Wipe, _u8L("Wipe"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
image_icon(window, pos, size, ImGui::LegendWipe);
|
||||
toggle_button(Preview::OptionType::Wipe, _u8L("Wipe"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
imgui.draw_icon(window, pos, size, ImGui::LegendWipe);
|
||||
});
|
||||
ImGui::SameLine();
|
||||
toggle_button(Preview::OptionType::Retractions, _u8L("Retractions"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
image_icon(window, pos, size, ImGui::LegendRetract);
|
||||
toggle_button(Preview::OptionType::Retractions, _u8L("Retractions"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
imgui.draw_icon(window, pos, size, ImGui::LegendRetract);
|
||||
});
|
||||
ImGui::SameLine();
|
||||
toggle_button(Preview::OptionType::Unretractions, _u8L("Deretractions"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
image_icon(window, pos, size, ImGui::LegendDeretract);
|
||||
toggle_button(Preview::OptionType::Unretractions, _u8L("Deretractions"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
imgui.draw_icon(window, pos, size, ImGui::LegendDeretract);
|
||||
});
|
||||
ImGui::SameLine();
|
||||
toggle_button(Preview::OptionType::Seams, _u8L("Seams"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
image_icon(window, pos, size, ImGui::LegendSeams);
|
||||
toggle_button(Preview::OptionType::Seams, _u8L("Seams"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
imgui.draw_icon(window, pos, size, ImGui::LegendSeams);
|
||||
});
|
||||
ImGui::SameLine();
|
||||
toggle_button(Preview::OptionType::ToolChanges, _u8L("Tool changes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
image_icon(window, pos, size, ImGui::LegendToolChanges);
|
||||
toggle_button(Preview::OptionType::ToolChanges, _u8L("Tool changes"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
imgui.draw_icon(window, pos, size, ImGui::LegendToolChanges);
|
||||
});
|
||||
ImGui::SameLine();
|
||||
toggle_button(Preview::OptionType::ColorChanges, _u8L("Color changes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
image_icon(window, pos, size, ImGui::LegendColorChanges);
|
||||
toggle_button(Preview::OptionType::ColorChanges, _u8L("Color changes"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
imgui.draw_icon(window, pos, size, ImGui::LegendColorChanges);
|
||||
});
|
||||
ImGui::SameLine();
|
||||
toggle_button(Preview::OptionType::PausePrints, _u8L("Print pauses"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
image_icon(window, pos, size, ImGui::LegendPausePrints);
|
||||
toggle_button(Preview::OptionType::PausePrints, _u8L("Print pauses"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
imgui.draw_icon(window, pos, size, ImGui::LegendPausePrints);
|
||||
});
|
||||
ImGui::SameLine();
|
||||
toggle_button(Preview::OptionType::CustomGCodes, _u8L("Custom G-codes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
image_icon(window, pos, size, ImGui::LegendCustomGCodes);
|
||||
toggle_button(Preview::OptionType::CustomGCodes, _u8L("Custom G-codes"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
imgui.draw_icon(window, pos, size, ImGui::LegendCustomGCodes);
|
||||
});
|
||||
ImGui::SameLine();
|
||||
toggle_button(Preview::OptionType::CenterOfGravity, _u8L("Center of gravity"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
image_icon(window, pos, size, ImGui::LegendCOG);
|
||||
toggle_button(Preview::OptionType::CenterOfGravity, _u8L("Center of gravity"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
imgui.draw_icon(window, pos, size, ImGui::LegendCOG);
|
||||
});
|
||||
ImGui::SameLine();
|
||||
if (!wxGetApp().is_gcode_viewer()) {
|
||||
toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
image_icon(window, pos, size, ImGui::LegendShells);
|
||||
toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
imgui.draw_icon(window, pos, size, ImGui::LegendShells);
|
||||
});
|
||||
ImGui::SameLine();
|
||||
}
|
||||
toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
image_icon(window, pos, size, ImGui::LegendToolMarker);
|
||||
toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
imgui.draw_icon(window, pos, size, ImGui::LegendToolMarker);
|
||||
});
|
||||
|
||||
bool size_dirty = !ImGui::GetCurrentWindow()->ScrollbarY && ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x != ImGui::GetWindowWidth();
|
||||
|
@ -1127,6 +1127,250 @@ void GLCanvas3D::load_arrange_settings()
|
||||
m_arrange_settings_fff_seq_print.alignment = arr_alignment ;
|
||||
}
|
||||
|
||||
static std::vector<int> processed_objects_idxs(const Model& model, const SLAPrint& sla_print, const GLVolumePtrs& volumes)
|
||||
{
|
||||
std::vector<int> ret;
|
||||
GLVolumePtrs matching_volumes;
|
||||
std::copy_if(volumes.begin(), volumes.end(), std::back_inserter(matching_volumes), [](GLVolume* v) {
|
||||
return v->volume_idx() == -(int)slaposDrillHoles; });
|
||||
for (const GLVolume* v : matching_volumes) {
|
||||
const int mo_idx = v->object_idx();
|
||||
const ModelObject* model_object = (mo_idx < (int)model.objects.size()) ? model.objects[mo_idx] : nullptr;
|
||||
if (model_object != nullptr && model_object->instances[v->instance_idx()]->is_printable()) {
|
||||
const SLAPrintObject* print_object = sla_print.get_print_object_by_model_object_id(model_object->id());
|
||||
if (print_object != nullptr && print_object->get_parts_to_slice().size() > 1)
|
||||
ret.push_back(mo_idx);
|
||||
}
|
||||
}
|
||||
std::sort(ret.begin(), ret.end());
|
||||
ret.erase(std::unique(ret.begin(), ret.end()), ret.end());
|
||||
return ret;
|
||||
};
|
||||
|
||||
static bool composite_id_match(const GLVolume::CompositeID& id1, const GLVolume::CompositeID& id2)
|
||||
{
|
||||
return id1.object_id == id2.object_id && id1.instance_id == id2.instance_id;
|
||||
}
|
||||
|
||||
static bool object_contains_negative_volumes(const Model& model, int obj_id) {
|
||||
return (0 <= obj_id && obj_id < (int)model.objects.size()) ? model.objects[obj_id]->has_negative_volume_mesh() : false;
|
||||
}
|
||||
|
||||
void GLCanvas3D::SLAView::detect_type_from_volumes(const GLVolumePtrs& volumes)
|
||||
{
|
||||
for (auto& [id, type] : m_instances_cache) {
|
||||
type = ESLAViewType::Original;
|
||||
}
|
||||
|
||||
for (const GLVolume* v : volumes) {
|
||||
if (v->volume_idx() == -(int)slaposDrillHoles) {
|
||||
if (object_contains_negative_volumes(*m_parent.get_model(), v->composite_id.object_id)) {
|
||||
const InstancesCacheItem* instance = find_instance_item(v->composite_id);
|
||||
assert(instance != nullptr);
|
||||
set_type(instance->first, ESLAViewType::Processed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GLCanvas3D::SLAView::set_type(ESLAViewType new_type)
|
||||
{
|
||||
for (auto& [id, type] : m_instances_cache) {
|
||||
type = new_type;
|
||||
if (new_type == ESLAViewType::Processed)
|
||||
select_full_instance(id);
|
||||
}
|
||||
}
|
||||
|
||||
void GLCanvas3D::SLAView::set_type(const GLVolume::CompositeID& id, ESLAViewType new_type)
|
||||
{
|
||||
InstancesCacheItem* instance = find_instance_item(id);
|
||||
assert(instance != nullptr);
|
||||
instance->second = new_type;
|
||||
if (new_type == ESLAViewType::Processed)
|
||||
select_full_instance(id);
|
||||
}
|
||||
|
||||
void GLCanvas3D::SLAView::update_volumes_visibility(GLVolumePtrs& volumes)
|
||||
{
|
||||
const SLAPrint* sla_print = m_parent.sla_print();
|
||||
const std::vector<int> mo_idxs = (sla_print != nullptr) ? processed_objects_idxs(*m_parent.get_model(), *sla_print, volumes) : std::vector<int>();
|
||||
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>>* raycasters = m_parent.get_raycasters_for_picking(SceneRaycaster::EType::Volume);
|
||||
|
||||
for (GLVolume* v : volumes) {
|
||||
const int obj_idx = v->object_idx();
|
||||
bool active = std::find(mo_idxs.begin(), mo_idxs.end(), obj_idx) == mo_idxs.end();
|
||||
if (!active) {
|
||||
const InstancesCacheItem* instance = find_instance_item(v->composite_id);
|
||||
assert(instance != nullptr);
|
||||
active = (instance->second == ESLAViewType::Processed) ? v->volume_idx() < 0 : v->volume_idx() != -(int)slaposDrillHoles;
|
||||
}
|
||||
v->is_active = active;
|
||||
auto it = std::find_if(raycasters->begin(), raycasters->end(), [v](std::shared_ptr<SceneRaycasterItem> item) { return item->get_raycaster() == v->mesh_raycaster.get(); });
|
||||
if (it != raycasters->end())
|
||||
(*it)->set_active(v->is_active);
|
||||
}
|
||||
}
|
||||
|
||||
void GLCanvas3D::SLAView::update_instances_cache(const std::vector<std::pair<GLVolume::CompositeID, GLVolume::CompositeID>>& new_to_old_ids_map)
|
||||
{
|
||||
// First, extract current instances list from the volumes
|
||||
const GLVolumePtrs& volumes = m_parent.get_volumes().volumes;
|
||||
std::vector<InstancesCacheItem> new_instances_cache;
|
||||
for (const GLVolume* v : volumes) {
|
||||
new_instances_cache.emplace_back(v->composite_id, ESLAViewType::Original);
|
||||
}
|
||||
|
||||
std::sort(new_instances_cache.begin(), new_instances_cache.end(),
|
||||
[](const InstancesCacheItem& i1, const InstancesCacheItem& i2) {
|
||||
return i1.first.object_id < i2.first.object_id || (i1.first.object_id == i2.first.object_id && i1.first.instance_id < i2.first.instance_id); });
|
||||
|
||||
new_instances_cache.erase(std::unique(new_instances_cache.begin(), new_instances_cache.end(),
|
||||
[](const InstancesCacheItem& i1, const InstancesCacheItem& i2) {
|
||||
return composite_id_match(i1.first, i2.first); }), new_instances_cache.end());
|
||||
|
||||
// Second, update instances type from previous state
|
||||
for (auto& inst_type : new_instances_cache) {
|
||||
const auto map_to_old_it = std::find_if(new_to_old_ids_map.begin(), new_to_old_ids_map.end(), [&inst_type](const std::pair<GLVolume::CompositeID, GLVolume::CompositeID>& item) {
|
||||
return composite_id_match(inst_type.first, item.first); });
|
||||
|
||||
const GLVolume::CompositeID old_inst_id = (map_to_old_it != new_to_old_ids_map.end()) ? map_to_old_it->second : inst_type.first;
|
||||
const InstancesCacheItem* old_instance = find_instance_item(old_inst_id);
|
||||
if (old_instance != nullptr)
|
||||
inst_type.second = old_instance->second;
|
||||
}
|
||||
|
||||
m_instances_cache = new_instances_cache;
|
||||
}
|
||||
|
||||
void GLCanvas3D::SLAView::render_switch_button()
|
||||
{
|
||||
const SLAPrint* sla_print = m_parent.sla_print();
|
||||
if (sla_print == nullptr)
|
||||
return;
|
||||
|
||||
const std::vector<int> mo_idxs = processed_objects_idxs(*m_parent.get_model(), *sla_print, m_parent.get_volumes().volumes);
|
||||
if (mo_idxs.empty())
|
||||
return;
|
||||
|
||||
Selection& selection = m_parent.get_selection();
|
||||
const int obj_idx = selection.get_object_idx();
|
||||
if (std::find(mo_idxs.begin(), mo_idxs.end(), obj_idx) == mo_idxs.end())
|
||||
return;
|
||||
|
||||
if (!object_contains_negative_volumes(*m_parent.get_model(), obj_idx))
|
||||
return;
|
||||
|
||||
const int inst_idx = selection.get_instance_idx();
|
||||
if (inst_idx < 0)
|
||||
return;
|
||||
|
||||
const GLVolume::CompositeID composite_id(obj_idx, 0, inst_idx);
|
||||
const InstancesCacheItem* sel_instance = find_instance_item(composite_id);
|
||||
if (sel_instance == nullptr)
|
||||
return;
|
||||
|
||||
const ESLAViewType type = sel_instance->second;
|
||||
|
||||
BoundingBoxf ss_box;
|
||||
if (m_use_instance_bbox) {
|
||||
const Selection::EMode mode = selection.get_mode();
|
||||
if (obj_idx >= 0 && inst_idx >= 0) {
|
||||
const Selection::IndicesList selected_idxs = selection.get_volume_idxs();
|
||||
std::vector<unsigned int> idxs_as_vector;
|
||||
idxs_as_vector.assign(selected_idxs.begin(), selected_idxs.end());
|
||||
selection.add_instance(obj_idx, inst_idx, true);
|
||||
ss_box = selection.get_screen_space_bounding_box();
|
||||
selection.add_volumes(mode, idxs_as_vector, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ss_box.defined)
|
||||
ss_box = selection.get_screen_space_bounding_box();
|
||||
assert(ss_box.defined);
|
||||
|
||||
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
ImGui::SetNextWindowPos(ImVec2((float)ss_box.max.x(), (float)ss_box.center().y()), ImGuiCond_Always, ImVec2(0.0, 0.5));
|
||||
imgui.begin(std::string("SLAViewSwitch"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration);
|
||||
const float icon_size = 1.5 * ImGui::GetTextLineHeight();
|
||||
if (imgui.draw_radio_button(_u8L("SLA view"), 1.5f * icon_size, true,
|
||||
[this, &imgui, sel_instance](ImGuiWindow& window, const ImVec2& pos, float size) {
|
||||
const wchar_t icon_id = (sel_instance->second == ESLAViewType::Original) ? ImGui::SlaViewProcessed : ImGui::SlaViewOriginal;
|
||||
imgui.draw_icon(window, pos, size, icon_id);
|
||||
})) {
|
||||
switch (sel_instance->second)
|
||||
{
|
||||
case ESLAViewType::Original: { m_parent.set_sla_view_type(sel_instance->first, ESLAViewType::Processed); break; }
|
||||
case ESLAViewType::Processed: { m_parent.set_sla_view_type(sel_instance->first, ESLAViewType::Original); break; }
|
||||
default: { assert(false); break; }
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
|
||||
ImGui::BeginTooltip();
|
||||
wxString tooltip;
|
||||
switch (type)
|
||||
{
|
||||
case ESLAViewType::Original: { tooltip = _L("Show as processed"); break; }
|
||||
case ESLAViewType::Processed: { tooltip = _L("Show as original"); break; }
|
||||
default: { assert(false); break; }
|
||||
}
|
||||
|
||||
imgui.text(tooltip);
|
||||
ImGui::EndTooltip();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
imgui.end();
|
||||
ImGui::PopStyleColor(2);
|
||||
}
|
||||
|
||||
#if ENABLE_SLA_VIEW_DEBUG_WINDOW
|
||||
void GLCanvas3D::SLAView::render_debug_window()
|
||||
{
|
||||
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
||||
imgui.begin(std::string("SLAView"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize);
|
||||
for (const auto& [id, type] : m_instances_cache) {
|
||||
imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "(" + std::to_string(id.object_id) + ", " + std::to_string(id.instance_id) + ")");
|
||||
ImGui::SameLine();
|
||||
imgui.text_colored(ImGui::GetStyleColorVec4(ImGuiCol_Text), (type == ESLAViewType::Original) ? "Original" : "Processed");
|
||||
}
|
||||
if (!m_instances_cache.empty())
|
||||
ImGui::Separator();
|
||||
|
||||
imgui.checkbox("Use instance bounding box", m_use_instance_bbox);
|
||||
imgui.end();
|
||||
}
|
||||
#endif // ENABLE_SLA_VIEW_DEBUG_WINDOW
|
||||
|
||||
GLCanvas3D::SLAView::InstancesCacheItem* GLCanvas3D::SLAView::find_instance_item(const GLVolume::CompositeID& id)
|
||||
{
|
||||
auto it = std::find_if(m_instances_cache.begin(), m_instances_cache.end(),
|
||||
[&id](const InstancesCacheItem& item) { return composite_id_match(item.first, id); });
|
||||
return (it == m_instances_cache.end()) ? nullptr : &(*it);
|
||||
}
|
||||
|
||||
void GLCanvas3D::SLAView::select_full_instance(const GLVolume::CompositeID& id)
|
||||
{
|
||||
bool extended_selection = false;
|
||||
Selection& selection = m_parent.get_selection();
|
||||
const Selection::ObjectIdxsToInstanceIdxsMap& sel_cache = selection.get_content();
|
||||
auto obj_it = sel_cache.find(id.object_id);
|
||||
if (obj_it != sel_cache.end()) {
|
||||
auto inst_it = std::find(obj_it->second.begin(), obj_it->second.end(), id.instance_id);
|
||||
if (inst_it != obj_it->second.end()) {
|
||||
selection.add_instance(id.object_id, id.instance_id);
|
||||
extended_selection = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (extended_selection)
|
||||
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
|
||||
}
|
||||
|
||||
PrinterTechnology GLCanvas3D::current_printer_technology() const
|
||||
{
|
||||
return m_process->current_printer_technology();
|
||||
@ -1168,6 +1412,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed)
|
||||
, m_render_sla_auxiliaries(true)
|
||||
, m_labels(*this)
|
||||
, m_slope(m_volumes)
|
||||
, m_sla_view(*this)
|
||||
{
|
||||
if (m_canvas != nullptr) {
|
||||
m_timer.SetOwner(m_canvas);
|
||||
@ -1774,6 +2019,15 @@ void GLCanvas3D::render()
|
||||
wxGetApp().obj_manipul()->render_debug_window();
|
||||
#endif // ENABLE_OBJECT_MANIPULATION_DEBUG
|
||||
|
||||
if (wxGetApp().plater()->is_view3D_shown() && current_printer_technology() == ptSLA) {
|
||||
const GLGizmosManager::EType type = m_gizmos.get_current_type();
|
||||
if (type == GLGizmosManager::EType::Undefined)
|
||||
m_sla_view.render_switch_button();
|
||||
#if ENABLE_SLA_VIEW_DEBUG_WINDOW
|
||||
m_sla_view.render_debug_window();
|
||||
#endif // ENABLE_SLA_VIEW_DEBUG_WINDOW
|
||||
}
|
||||
|
||||
std::string tooltip;
|
||||
|
||||
// Negative coordinate means out of the window, likely because the window was deactivated.
|
||||
@ -2013,6 +2267,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
size_t volume_idx;
|
||||
};
|
||||
|
||||
std::vector<std::pair<GLVolume::CompositeID, GLVolume::CompositeID>> new_to_old_ids_map;
|
||||
|
||||
// SLA steps to pull the preview meshes for.
|
||||
typedef std::array<SLAPrintObjectStep, 3> SLASteps;
|
||||
SLASteps sla_steps = { slaposDrillHoles, slaposSupportTree, slaposPad };
|
||||
@ -2160,12 +2416,12 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++ obj_idx) {
|
||||
const ModelObject &model_object = *m_model->objects[obj_idx];
|
||||
for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++ volume_idx) {
|
||||
const ModelVolume &model_volume = *model_object.volumes[volume_idx];
|
||||
const ModelVolume &model_volume = *model_object.volumes[volume_idx];
|
||||
for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) {
|
||||
const ModelInstance &model_instance = *model_object.instances[instance_idx];
|
||||
ModelVolumeState key(model_volume.id(), model_instance.id());
|
||||
auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower);
|
||||
assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id);
|
||||
const ModelInstance &model_instance = *model_object.instances[instance_idx];
|
||||
ModelVolumeState key(model_volume.id(), model_instance.id());
|
||||
auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower);
|
||||
assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id);
|
||||
if (it->new_geometry()) {
|
||||
// New volume.
|
||||
auto it_old_volume = std::lower_bound(deleted_volumes.begin(), deleted_volumes.end(), GLVolumeState(it->composite_id), deleted_volumes_lower);
|
||||
@ -2178,15 +2434,17 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx);
|
||||
m_volumes.volumes.back()->geometry_id = key.geometry_id;
|
||||
update_object_list = true;
|
||||
} else {
|
||||
// Recycling an old GLVolume.
|
||||
GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx];
|
||||
}
|
||||
else {
|
||||
// Recycling an old GLVolume.
|
||||
GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx];
|
||||
assert(existing_volume.geometry_id == key.geometry_id);
|
||||
// Update the Object/Volume/Instance indices into the current Model.
|
||||
if (existing_volume.composite_id != it->composite_id) {
|
||||
existing_volume.composite_id = it->composite_id;
|
||||
update_object_list = true;
|
||||
}
|
||||
// Update the Object/Volume/Instance indices into the current Model.
|
||||
if (existing_volume.composite_id != it->composite_id) {
|
||||
new_to_old_ids_map.push_back(std::make_pair(it->composite_id, existing_volume.composite_id));
|
||||
existing_volume.composite_id = it->composite_id;
|
||||
update_object_list = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2237,7 +2495,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
}
|
||||
else {
|
||||
// Recycling an old GLVolume. Update the Object/Instance indices into the current Model.
|
||||
m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx);
|
||||
const GLVolume::CompositeID new_id(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx);
|
||||
new_to_old_ids_map.push_back(std::make_pair(new_id, m_volumes.volumes[it->volume_idx]->composite_id));
|
||||
m_volumes.volumes[it->volume_idx]->composite_id = new_id;
|
||||
m_volumes.volumes[it->volume_idx]->set_instance_transformation(model_object->instances[instance_idx]->get_transformation());
|
||||
}
|
||||
}
|
||||
@ -2305,6 +2565,24 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
else
|
||||
m_selection.volumes_changed(map_glvolume_old_to_new);
|
||||
|
||||
if (printer_technology == ptSLA) {
|
||||
std::sort(new_to_old_ids_map.begin(), new_to_old_ids_map.end(),
|
||||
[](const std::pair<GLVolume::CompositeID, GLVolume::CompositeID>& i1, const std::pair<GLVolume::CompositeID, GLVolume::CompositeID>& i2) {
|
||||
return i1.first.object_id < i2.first.object_id || (i1.first.object_id == i2.first.object_id && i1.first.instance_id < i2.first.instance_id); });
|
||||
|
||||
new_to_old_ids_map.erase(std::unique(new_to_old_ids_map.begin(), new_to_old_ids_map.end(),
|
||||
[](const std::pair<GLVolume::CompositeID, GLVolume::CompositeID>& i1, const std::pair<GLVolume::CompositeID, GLVolume::CompositeID>& i2) {
|
||||
return composite_id_match(i1.first, i2.first); }), new_to_old_ids_map.end());
|
||||
|
||||
m_sla_view.update_instances_cache(new_to_old_ids_map);
|
||||
if (m_sla_view_type_detection_active) {
|
||||
m_sla_view.detect_type_from_volumes(m_volumes.volumes);
|
||||
m_sla_view_type_detection_active = false;
|
||||
}
|
||||
m_sla_view.update_volumes_visibility(m_volumes.volumes);
|
||||
update_object_list = true;
|
||||
}
|
||||
|
||||
m_gizmos.update_data();
|
||||
m_gizmos.refresh_on_off_state();
|
||||
|
||||
@ -4387,6 +4665,20 @@ std::pair<SlicingParameters, const std::vector<double>> GLCanvas3D::get_layers_h
|
||||
return ret;
|
||||
}
|
||||
|
||||
void GLCanvas3D::set_sla_view_type(ESLAViewType type)
|
||||
{
|
||||
m_sla_view.set_type(type);
|
||||
m_sla_view.update_volumes_visibility(m_volumes.volumes);
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
void GLCanvas3D::set_sla_view_type(const GLVolume::CompositeID& id, ESLAViewType type)
|
||||
{
|
||||
m_sla_view.set_type(id, type);
|
||||
m_sla_view.update_volumes_visibility(m_volumes.volumes);
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
bool GLCanvas3D::_is_shown_on_screen() const
|
||||
{
|
||||
return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false;
|
||||
|
@ -466,6 +466,12 @@ public:
|
||||
int alignment = 0;
|
||||
};
|
||||
|
||||
enum class ESLAViewType
|
||||
{
|
||||
Original,
|
||||
Processed
|
||||
};
|
||||
|
||||
private:
|
||||
wxGLCanvas* m_canvas;
|
||||
wxGLContext* m_context;
|
||||
@ -545,11 +551,37 @@ private:
|
||||
bool m_tooltip_enabled{ true };
|
||||
Slope m_slope;
|
||||
|
||||
class SLAView
|
||||
{
|
||||
public:
|
||||
explicit SLAView(GLCanvas3D& parent) : m_parent(parent) {}
|
||||
void detect_type_from_volumes(const GLVolumePtrs& volumes);
|
||||
void set_type(ESLAViewType type);
|
||||
void set_type(const GLVolume::CompositeID& id, ESLAViewType type);
|
||||
void update_volumes_visibility(GLVolumePtrs& volumes);
|
||||
void update_instances_cache(const std::vector<std::pair<GLVolume::CompositeID, GLVolume::CompositeID>>& new_to_old_ids_map);
|
||||
void render_switch_button();
|
||||
|
||||
#if ENABLE_SLA_VIEW_DEBUG_WINDOW
|
||||
void render_debug_window();
|
||||
#endif // ENABLE_SLA_VIEW_DEBUG_WINDOW
|
||||
|
||||
private:
|
||||
GLCanvas3D& m_parent;
|
||||
typedef std::pair<GLVolume::CompositeID, ESLAViewType> InstancesCacheItem;
|
||||
std::vector<InstancesCacheItem> m_instances_cache;
|
||||
bool m_use_instance_bbox{ true };
|
||||
|
||||
InstancesCacheItem* find_instance_item(const GLVolume::CompositeID& id);
|
||||
void select_full_instance(const GLVolume::CompositeID& id);
|
||||
};
|
||||
|
||||
SLAView m_sla_view;
|
||||
bool m_sla_view_type_detection_active{ false };
|
||||
|
||||
ArrangeSettings m_arrange_settings_fff, m_arrange_settings_sla,
|
||||
m_arrange_settings_fff_seq_print;
|
||||
|
||||
PrinterTechnology current_printer_technology() const;
|
||||
|
||||
bool is_arrange_alignment_enabled() const;
|
||||
|
||||
template<class Self>
|
||||
@ -666,7 +698,7 @@ private:
|
||||
GLModel m_background;
|
||||
|
||||
public:
|
||||
explicit GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed);
|
||||
GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed);
|
||||
~GLCanvas3D();
|
||||
|
||||
bool is_initialized() const { return m_initialized; }
|
||||
@ -782,6 +814,8 @@ public:
|
||||
void zoom_to_gcode();
|
||||
void select_view(const std::string& direction);
|
||||
|
||||
PrinterTechnology current_printer_technology() const;
|
||||
|
||||
void update_volumes_colors_by_extruder();
|
||||
|
||||
bool is_dragging() const { return m_gizmos.is_dragging() || (m_moving && !m_mouse.scene_position.isApprox(m_mouse.drag.start_position_3D)); }
|
||||
@ -968,6 +1002,10 @@ public:
|
||||
|
||||
std::pair<SlicingParameters, const std::vector<double>> get_layers_height_data(int object_id);
|
||||
|
||||
void set_sla_view_type(ESLAViewType type);
|
||||
void set_sla_view_type(const GLVolume::CompositeID& id, ESLAViewType type);
|
||||
void enable_sla_view_type_detection() { m_sla_view_type_detection_active = true; }
|
||||
|
||||
private:
|
||||
bool _is_shown_on_screen() const;
|
||||
|
||||
|
@ -3057,6 +3057,13 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage
|
||||
auto wizard = new ConfigWizard(mainframe);
|
||||
const bool res = wizard->run(reason, start_page);
|
||||
|
||||
// !!! Deallocate memory after close ConfigWizard.
|
||||
// Note, that mainframe is a parent of ConfigWizard.
|
||||
// So, wizard will be destroyed only during destroying of mainframe
|
||||
// To avoid this state the wizard have to be disconnected from mainframe and Destroyed explicitly
|
||||
mainframe->RemoveChild(wizard);
|
||||
wizard->Destroy();
|
||||
|
||||
if (res) {
|
||||
load_current_presets();
|
||||
|
||||
|
@ -758,6 +758,10 @@ void ObjectList::selection_changed()
|
||||
wxGetApp().obj_layers()->update_scene_from_editor_selection();
|
||||
}
|
||||
}
|
||||
else if (type & itVolume) {
|
||||
if (printer_technology() == ptSLA)
|
||||
wxGetApp().plater()->canvas3D()->set_sla_view_type(scene_selection().get_first_volume()->composite_id, GLCanvas3D::ESLAViewType::Original);
|
||||
}
|
||||
}
|
||||
|
||||
part_selection_changed();
|
||||
|
@ -15,6 +15,8 @@
|
||||
#include "MainFrame.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
|
||||
#include <wx/glcanvas.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include "slic3r/Utils/FixModelByWin10.hpp"
|
||||
|
||||
@ -519,19 +521,18 @@ void ObjectManipulation::UpdateAndShow(const bool show)
|
||||
OG_Settings::UpdateAndShow(show);
|
||||
}
|
||||
|
||||
void ObjectManipulation::Enable(const bool enadle)
|
||||
void ObjectManipulation::Enable(const bool enable)
|
||||
{
|
||||
for (auto editor : m_editors)
|
||||
editor->Enable(enadle);
|
||||
m_is_enabled = m_is_enabled_size_and_scale = enable;
|
||||
for (wxWindow* win : std::initializer_list<wxWindow*>{ m_reset_scale_button, m_reset_rotation_button, m_drop_to_bed_button, m_check_inch, m_lock_bnt
|
||||
, m_reset_skew_button })
|
||||
win->Enable(enadle);
|
||||
win->Enable(enable);
|
||||
}
|
||||
|
||||
void ObjectManipulation::DisableScale()
|
||||
{
|
||||
for (auto editor : m_editors)
|
||||
editor->Enable(editor->has_opt_key("scale") || editor->has_opt_key("size") ? false : true);
|
||||
m_is_enabled = true;
|
||||
m_is_enabled_size_and_scale = false;
|
||||
for (wxWindow* win : std::initializer_list<wxWindow*>{ m_reset_scale_button, m_lock_bnt, m_reset_skew_button })
|
||||
win->Enable(false);
|
||||
}
|
||||
@ -1229,6 +1230,12 @@ ManipulationEditor::ManipulationEditor(ObjectManipulation* parent,
|
||||
this->SetSelection(-1, -1); //select all
|
||||
event.Skip();
|
||||
}));
|
||||
|
||||
this->Bind(wxEVT_UPDATE_UI, [parent, this](wxUpdateUIEvent& evt) {
|
||||
const bool is_gizmo_in_editing_mode = wxGetApp().plater()->canvas3D()->get_gizmos_manager().is_in_editing_mode();
|
||||
const bool is_enabled_editing = has_opt_key("scale") || has_opt_key("size") ? parent->is_enabled_size_and_scale() : true;
|
||||
evt.Enable(!is_gizmo_in_editing_mode && parent->is_enabled() && is_enabled_editing);
|
||||
});
|
||||
}
|
||||
|
||||
void ManipulationEditor::msw_rescale()
|
||||
|
@ -165,6 +165,11 @@ private:
|
||||
|
||||
std::vector<ManipulationEditor*> m_editors;
|
||||
|
||||
// parameters for enabling/disabling of editors
|
||||
bool m_is_enabled { true };
|
||||
bool m_is_enabled_size_and_scale { true };
|
||||
|
||||
|
||||
public:
|
||||
ObjectManipulation(wxWindow* parent);
|
||||
~ObjectManipulation() {}
|
||||
@ -213,6 +218,9 @@ public:
|
||||
|
||||
static wxString coordinate_type_str(ECoordinatesType type);
|
||||
|
||||
bool is_enabled() const { return m_is_enabled; }
|
||||
bool is_enabled_size_and_scale()const { return m_is_enabled_size_and_scale; }
|
||||
|
||||
#if ENABLE_OBJECT_MANIPULATION_DEBUG
|
||||
void render_debug_window();
|
||||
#endif // ENABLE_OBJECT_MANIPULATION_DEBUG
|
||||
|
@ -20,8 +20,15 @@ GLGizmoSlaBase::GLGizmoSlaBase(GLCanvas3D& parent, const std::string& icon_filen
|
||||
void GLGizmoSlaBase::reslice_until_step(SLAPrintObjectStep step, bool postpone_error_messages)
|
||||
{
|
||||
wxGetApp().CallAfter([this, step, postpone_error_messages]() {
|
||||
wxGetApp().plater()->reslice_SLA_until_step(step, *m_c->selection_info()->model_object(), postpone_error_messages);
|
||||
});
|
||||
if (m_c->selection_info())
|
||||
wxGetApp().plater()->reslice_SLA_until_step(step, *m_c->selection_info()->model_object(), postpone_error_messages);
|
||||
else {
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
const int object_idx = selection.get_object_idx();
|
||||
if (object_idx >= 0 && !selection.is_wipe_tower())
|
||||
wxGetApp().plater()->reslice_SLA_until_step(step, *wxGetApp().plater()->model().objects[object_idx], postpone_error_messages);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
CommonGizmosDataID GLGizmoSlaBase::on_get_requirements() const
|
||||
|
@ -87,6 +87,8 @@ void GLGizmoSlaSupports::data_changed(bool is_serializing)
|
||||
register_point_raycasters_for_picking();
|
||||
else
|
||||
update_point_raycasters_for_picking_transform();
|
||||
|
||||
m_c->instances_hider()->set_hide_full_scene(true);
|
||||
}
|
||||
|
||||
// m_parent.toggle_model_objects_visibility(false);
|
||||
@ -399,7 +401,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
}
|
||||
|
||||
if (action == SLAGizmoEventType::DiscardChanges) {
|
||||
ask_about_changes_call_after([this](){ editing_mode_apply_changes(); },
|
||||
ask_about_changes([this](){ editing_mode_apply_changes(); },
|
||||
[this](){ editing_mode_discard_changes(); });
|
||||
return true;
|
||||
}
|
||||
@ -816,39 +818,36 @@ std::string GLGizmoSlaSupports::on_get_name() const
|
||||
return _u8L("SLA Support Points");
|
||||
}
|
||||
|
||||
void GLGizmoSlaSupports::ask_about_changes_call_after(std::function<void()> on_yes, std::function<void()> on_no)
|
||||
bool GLGizmoSlaSupports::ask_about_changes(std::function<void()> on_yes, std::function<void()> on_no)
|
||||
{
|
||||
wxGetApp().CallAfter([on_yes, on_no]() {
|
||||
// Following is called through CallAfter, because otherwise there was a problem
|
||||
// on OSX with the wxMessageDialog being shown several times when clicked into.
|
||||
MessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually "
|
||||
"edited support points?") + "\n",_L("Save support points?"), wxICON_QUESTION | wxYES | wxNO | wxCANCEL );
|
||||
int ret = dlg.ShowModal();
|
||||
if (ret == wxID_YES)
|
||||
on_yes();
|
||||
else if (ret == wxID_NO)
|
||||
on_no();
|
||||
});
|
||||
}
|
||||
MessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually edited support points?") + "\n",
|
||||
_L("Save support points?"), wxICON_QUESTION | wxYES | wxNO | wxCANCEL );
|
||||
|
||||
const int ret = dlg.ShowModal();
|
||||
if (ret == wxID_YES)
|
||||
on_yes();
|
||||
else if (ret == wxID_NO)
|
||||
on_no();
|
||||
else
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GLGizmoSlaSupports::on_set_state()
|
||||
{
|
||||
if (m_state == m_old_state)
|
||||
return;
|
||||
|
||||
if (m_state == On && m_old_state != On) { // the gizmo was just turned on
|
||||
if (m_state == On) { // the gizmo was just turned on
|
||||
// Set default head diameter from config.
|
||||
const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
|
||||
m_new_point_head_diameter = static_cast<const ConfigOptionFloat*>(cfg.option("support_head_front_diameter"))->value;
|
||||
}
|
||||
if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
|
||||
bool will_ask = m_editing_mode && unsaved_changes() && on_is_activable();
|
||||
if (will_ask) {
|
||||
ask_about_changes_call_after([this](){ editing_mode_apply_changes(); },
|
||||
[this](){ editing_mode_discard_changes(); });
|
||||
// refuse to be turned off so the gizmo is active when the CallAfter is executed
|
||||
m_state = m_old_state;
|
||||
else {
|
||||
if (m_editing_mode && unsaved_changes() && on_is_activable()) {
|
||||
if (!ask_about_changes([this]() { editing_mode_apply_changes(); },
|
||||
[this]() { editing_mode_discard_changes(); })) {
|
||||
m_state = On;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// we are actually shutting down
|
||||
@ -856,16 +855,12 @@ void GLGizmoSlaSupports::on_set_state()
|
||||
m_old_mo_id = -1;
|
||||
}
|
||||
|
||||
if (m_state == Off) {
|
||||
m_c->instances_hider()->set_hide_full_scene(false);
|
||||
m_c->selection_info()->set_use_shift(false); // see top of on_render for details
|
||||
}
|
||||
m_c->instances_hider()->set_hide_full_scene(false);
|
||||
m_c->selection_info()->set_use_shift(false); // see top of on_render for details
|
||||
|
||||
}
|
||||
m_old_state = m_state;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GLGizmoSlaSupports::on_start_dragging()
|
||||
{
|
||||
if (m_hover_id != -1) {
|
||||
|
@ -110,7 +110,6 @@ private:
|
||||
|
||||
bool m_wait_for_up_event = false;
|
||||
bool m_selection_empty = true;
|
||||
EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
|
||||
|
||||
std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const;
|
||||
bool is_mesh_point_clipped(const Vec3d& point) const;
|
||||
@ -131,7 +130,9 @@ private:
|
||||
void auto_generate();
|
||||
void switch_to_editing_mode();
|
||||
void disable_editing_mode();
|
||||
void ask_about_changes_call_after(std::function<void()> on_yes, std::function<void()> on_no);
|
||||
|
||||
// return false if Cancel was selected
|
||||
bool ask_about_changes(std::function<void()> on_yes, std::function<void()> on_no);
|
||||
|
||||
protected:
|
||||
void on_set_state() override;
|
||||
|
@ -173,7 +173,8 @@ void GLGizmosManager::reset_all_states()
|
||||
const EType current = get_current_type();
|
||||
if (current != Undefined)
|
||||
// close any open gizmo
|
||||
open_gizmo(current);
|
||||
if (!open_gizmo(current))
|
||||
return;
|
||||
|
||||
activate_gizmo(Undefined);
|
||||
m_hover = Undefined;
|
||||
@ -978,6 +979,9 @@ bool GLGizmosManager::activate_gizmo(EType type)
|
||||
return false; // gizmo refused to be turned on.
|
||||
}
|
||||
|
||||
if (m_parent.current_printer_technology() == ptSLA)
|
||||
m_parent.set_sla_view_type(GLCanvas3D::ESLAViewType::Original);
|
||||
|
||||
new_gizmo.register_raycasters_for_picking();
|
||||
|
||||
// sucessful activation of gizmo
|
||||
|
@ -104,6 +104,8 @@ static const std::map<const wchar_t, std::string> font_icons_large = {
|
||||
{ImGui::PauseHoverButton , "notification_pause_hover" },
|
||||
{ImGui::OpenButton , "notification_open" },
|
||||
{ImGui::OpenHoverButton , "notification_open_hover" },
|
||||
{ImGui::SlaViewOriginal , "sla_view_original" },
|
||||
{ImGui::SlaViewProcessed , "sla_view_processed" },
|
||||
};
|
||||
|
||||
static const std::map<const wchar_t, std::string> font_icons_extra_large = {
|
||||
@ -490,6 +492,18 @@ bool ImGuiWrapper::radio_button(const wxString &label, bool active)
|
||||
return ImGui::RadioButton(label_utf8.c_str(), active);
|
||||
}
|
||||
|
||||
void ImGuiWrapper::draw_icon(ImGuiWindow& window, const ImVec2& pos, float size, wchar_t icon_id)
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
const ImTextureID tex_id = io.Fonts->TexID;
|
||||
const float tex_w = static_cast<float>(io.Fonts->TexWidth);
|
||||
const float tex_h = static_cast<float>(io.Fonts->TexHeight);
|
||||
const ImFontAtlas::CustomRect* const rect = GetTextureCustomRect(icon_id);
|
||||
const ImVec2 uv0 = { static_cast<float>(rect->X) / tex_w, static_cast<float>(rect->Y) / tex_h };
|
||||
const ImVec2 uv1 = { static_cast<float>(rect->X + rect->Width) / tex_w, static_cast<float>(rect->Y + rect->Height) / tex_h };
|
||||
window.DrawList->AddImage(tex_id, pos, { pos.x + size, pos.y + size }, uv0, uv1, ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f }));
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::draw_radio_button(const std::string& name, float size, bool active,
|
||||
std::function<void(ImGuiWindow& window, const ImVec2& pos, float size)> draw_callback)
|
||||
{
|
||||
|
@ -92,9 +92,10 @@ public:
|
||||
void end();
|
||||
|
||||
bool button(const wxString &label, const wxString& tooltip = {});
|
||||
bool button(const wxString& label, float width, float height);
|
||||
bool button(const wxString& label, float width, float height);
|
||||
bool button(const wxString& label, const ImVec2 &size, bool enable); // default size = ImVec2(0.f, 0.f)
|
||||
bool radio_button(const wxString &label, bool active);
|
||||
void draw_icon(ImGuiWindow& window, const ImVec2& pos, float size, wchar_t icon_id);
|
||||
bool draw_radio_button(const std::string& name, float size, bool active, std::function<void(ImGuiWindow& window, const ImVec2& pos, float size)> draw_callback);
|
||||
bool checkbox(const wxString &label, bool &value);
|
||||
static void text(const char *label);
|
||||
|
@ -1068,12 +1068,6 @@ void Sidebar::update_presets(Preset::Type preset_type)
|
||||
dynamic_cast<ConfigOptionFloats*>(preset_bundle.printers.get_edited_preset().config.option("nozzle_diameter"))->values.size();
|
||||
const size_t filament_cnt = p->combos_filament.size() > extruder_cnt ? extruder_cnt : p->combos_filament.size();
|
||||
|
||||
if (filament_cnt == 1) {
|
||||
// Single filament printer, synchronize the filament presets.
|
||||
const std::string &name = preset_bundle.filaments.get_selected_preset_name();
|
||||
preset_bundle.set_filament_preset(0, name);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < filament_cnt; i++)
|
||||
p->combos_filament[i]->update();
|
||||
|
||||
@ -1417,14 +1411,14 @@ void Sidebar::update_sliced_info_sizer()
|
||||
new_label = _L("Used Filament (g)");
|
||||
info_text = wxString::Format("%.2f", ps.total_weight);
|
||||
|
||||
const std::vector<std::string>& filament_presets = wxGetApp().preset_bundle->filament_presets;
|
||||
const auto& extruders_filaments = wxGetApp().preset_bundle->extruders_filaments;
|
||||
const PresetCollection& filaments = wxGetApp().preset_bundle->filaments;
|
||||
|
||||
if (ps.filament_stats.size() > 1)
|
||||
new_label += ":";
|
||||
|
||||
for (auto filament : ps.filament_stats) {
|
||||
const Preset* filament_preset = filaments.find_preset(filament_presets[filament.first], false);
|
||||
const Preset* filament_preset = filaments.find_preset(extruders_filaments[filament.first].get_selected_preset_name(), false);
|
||||
if (filament_preset) {
|
||||
double filament_weight;
|
||||
if (ps.filament_stats.size() == 1)
|
||||
@ -2405,8 +2399,8 @@ void Plater::check_selected_presets_visibility(PrinterTechnology loaded_printer_
|
||||
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
|
||||
if (loaded_printer_technology == ptFFF) {
|
||||
update_selected_preset_visibility(preset_bundle->prints, names);
|
||||
for (const std::string& filament : preset_bundle->filament_presets) {
|
||||
Preset* preset = preset_bundle->filaments.find_preset(filament);
|
||||
for (const auto& extruder_filaments : preset_bundle->extruders_filaments) {
|
||||
Preset* preset = preset_bundle->filaments.find_preset(extruder_filaments.get_selected_preset_name());
|
||||
if (preset && !preset->is_visible) {
|
||||
preset->is_visible = true;
|
||||
names.emplace_back(preset->name);
|
||||
@ -4032,19 +4026,25 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
|
||||
//! combo->GetStringSelection().ToUTF8().data());
|
||||
|
||||
std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type,
|
||||
Preset::remove_suffix_modified(combo->GetString(selection).ToUTF8().data()));
|
||||
|
||||
if (preset_type == Preset::TYPE_FILAMENT) {
|
||||
wxGetApp().preset_bundle->set_filament_preset(idx, preset_name);
|
||||
}
|
||||
Preset::remove_suffix_modified(into_u8(combo->GetString(selection))), idx);
|
||||
|
||||
std::string last_selected_ph_printer_name = combo->get_selected_ph_printer_name();
|
||||
|
||||
bool select_preset = !combo->selection_is_changed_according_to_physical_printers();
|
||||
// TODO: ?
|
||||
if (preset_type == Preset::TYPE_FILAMENT && sidebar->is_multifilament()) {
|
||||
// Only update the plater UI for the 2nd and other filaments.
|
||||
combo->update();
|
||||
if (preset_type == Preset::TYPE_FILAMENT) {
|
||||
wxGetApp().preset_bundle->set_filament_preset(idx, preset_name);
|
||||
|
||||
TabFilament* tab = dynamic_cast<TabFilament*>(wxGetApp().get_tab(Preset::TYPE_FILAMENT));
|
||||
if (tab && combo->get_extruder_idx() == tab->get_active_extruder() && !tab->select_preset(preset_name)) {
|
||||
// revert previously selection
|
||||
const std::string& old_name = wxGetApp().preset_bundle->filaments.get_edited_preset().name;
|
||||
wxGetApp().preset_bundle->set_filament_preset(idx, old_name);
|
||||
combo->update();
|
||||
}
|
||||
else
|
||||
// Synchronize config.ini with the current selections.
|
||||
wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config);
|
||||
}
|
||||
else if (select_preset) {
|
||||
wxWindowUpdateLocker noUpdates(sidebar->presets_panel());
|
||||
@ -4094,8 +4094,10 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
|
||||
// If RELOAD_SLA_SUPPORT_POINTS, then the SLA gizmo is updated (reload_scene calls update_gizmos_data)
|
||||
if (view3D->is_dragging())
|
||||
delayed_scene_refresh = true;
|
||||
else
|
||||
else {
|
||||
view3D->get_canvas3d()->enable_sla_view_type_detection();
|
||||
this->update_sla_scene();
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
@ -6897,6 +6899,8 @@ void Plater::on_extruders_change(size_t num_extruders)
|
||||
if (num_extruders == choices.size())
|
||||
return;
|
||||
|
||||
dynamic_cast<TabFilament*>(wxGetApp().get_tab(Preset::TYPE_FILAMENT))->update_extruder_combobox();
|
||||
|
||||
wxWindowUpdateLocker noUpdates_scrolled_panel(&sidebar()/*.scrolled_panel()*/);
|
||||
|
||||
size_t i = choices.size();
|
||||
@ -6923,16 +6927,16 @@ bool Plater::update_filament_colors_in_full_config()
|
||||
// There is a case, when we use filament_color instead of extruder_color (when extruder_color == "").
|
||||
// Thus plater config option "filament_colour" should be filled with filament_presets values.
|
||||
// Otherwise, on 3dScene will be used last edited filament color for all volumes with extruder_color == "".
|
||||
const std::vector<std::string> filament_presets = wxGetApp().preset_bundle->filament_presets;
|
||||
if (filament_presets.size() == 1 || !p->config->has("filament_colour"))
|
||||
const auto& extruders_filaments = wxGetApp().preset_bundle->extruders_filaments;
|
||||
if (extruders_filaments.size() == 1 || !p->config->has("filament_colour"))
|
||||
return false;
|
||||
|
||||
const PresetCollection& filaments = wxGetApp().preset_bundle->filaments;
|
||||
std::vector<std::string> filament_colors;
|
||||
filament_colors.reserve(filament_presets.size());
|
||||
filament_colors.reserve(extruders_filaments.size());
|
||||
|
||||
for (const std::string& filament_preset : filament_presets)
|
||||
filament_colors.push_back(filaments.find_preset(filament_preset, true)->config.opt_string("filament_colour", (unsigned)0));
|
||||
for (const auto& extr_filaments : extruders_filaments)
|
||||
filament_colors.push_back(filaments.find_preset(extr_filaments.get_selected_preset_name(), true)->config.opt_string("filament_colour", (unsigned)0));
|
||||
|
||||
p->config->option<ConfigOptionStrings>("filament_colour")->values = filament_colors;
|
||||
return true;
|
||||
@ -6957,10 +6961,12 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
|
||||
|
||||
p->config->set_key_value(opt_key, config.option(opt_key)->clone());
|
||||
if (opt_key == "printer_technology") {
|
||||
this->set_printer_technology(config.opt_enum<PrinterTechnology>(opt_key));
|
||||
const PrinterTechnology printer_technology = config.opt_enum<PrinterTechnology>(opt_key);
|
||||
this->set_printer_technology(printer_technology);
|
||||
p->sidebar->show_sliced_info_sizer(false);
|
||||
p->reset_gcode_toolpaths();
|
||||
p->view3D->get_canvas3d()->reset_sequential_print_clearance();
|
||||
p->view3D->get_canvas3d()->set_sla_view_type(GLCanvas3D::ESLAViewType::Original);
|
||||
}
|
||||
else if (opt_key == "bed_shape" || opt_key == "bed_custom_texture" || opt_key == "bed_custom_model") {
|
||||
bed_shape_changed = true;
|
||||
@ -7025,16 +7031,17 @@ void Plater::force_filament_colors_update()
|
||||
{
|
||||
bool update_scheduled = false;
|
||||
DynamicPrintConfig* config = p->config;
|
||||
const std::vector<std::string> filament_presets = wxGetApp().preset_bundle->filament_presets;
|
||||
if (filament_presets.size() > 1 &&
|
||||
p->config->option<ConfigOptionStrings>("filament_colour")->values.size() == filament_presets.size())
|
||||
|
||||
const auto& extruders_filaments = wxGetApp().preset_bundle->extruders_filaments;
|
||||
if (extruders_filaments.size() > 1 &&
|
||||
p->config->option<ConfigOptionStrings>("filament_colour")->values.size() == extruders_filaments.size())
|
||||
{
|
||||
const PresetCollection& filaments = wxGetApp().preset_bundle->filaments;
|
||||
std::vector<std::string> filament_colors;
|
||||
filament_colors.reserve(filament_presets.size());
|
||||
filament_colors.reserve(extruders_filaments.size());
|
||||
|
||||
for (const std::string& filament_preset : filament_presets)
|
||||
filament_colors.push_back(filaments.find_preset(filament_preset, true)->config.opt_string("filament_colour", (unsigned)0));
|
||||
for (const auto& extr_filaments : extruders_filaments)
|
||||
filament_colors.push_back(extr_filaments.get_selected_preset()->config.opt_string("filament_colour", (unsigned)0));
|
||||
|
||||
if (config->option<ConfigOptionStrings>("filament_colour")->values != filament_colors) {
|
||||
config->option<ConfigOptionStrings>("filament_colour")->values = filament_colors;
|
||||
@ -7051,6 +7058,20 @@ void Plater::force_filament_colors_update()
|
||||
this->p->schedule_background_process();
|
||||
}
|
||||
|
||||
void Plater::force_filament_cb_update()
|
||||
{
|
||||
// Update visibility for templates presets according to app_config
|
||||
PresetCollection& filaments = wxGetApp().preset_bundle->filaments;
|
||||
AppConfig& config = *wxGetApp().app_config;
|
||||
for (Preset& preset : filaments)
|
||||
preset.set_visible_from_appconfig(config);
|
||||
wxGetApp().preset_bundle->update_compatible(PresetSelectCompatibleType::Never, PresetSelectCompatibleType::OnlyIfWasCompatible);
|
||||
|
||||
// Update preset comboboxes on sidebar and filaments tab
|
||||
p->sidebar->update_presets(Preset::TYPE_FILAMENT);
|
||||
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->select_preset(wxGetApp().preset_bundle->filaments.get_selected_preset_name());
|
||||
}
|
||||
|
||||
void Plater::force_print_bed_update()
|
||||
{
|
||||
// Fill in the printer model key with something which cannot possibly be valid, so that Plater::on_config_change() will update the print bed
|
||||
|
@ -308,6 +308,7 @@ public:
|
||||
bool update_filament_colors_in_full_config();
|
||||
void on_config_change(const DynamicPrintConfig &config);
|
||||
void force_filament_colors_update();
|
||||
void force_filament_cb_update();
|
||||
void force_print_bed_update();
|
||||
// On activating the parent window.
|
||||
void on_activate();
|
||||
|
@ -694,8 +694,6 @@ void PreferencesDialog::accept(wxEvent&)
|
||||
#endif // __linux__
|
||||
}
|
||||
|
||||
bool update_filament_sidebar = (m_values.find("no_templates") != m_values.end());
|
||||
|
||||
std::vector<std::string> options_to_recreate_GUI = { "no_defaults", "tabs_as_menu", "sys_menu_enabled", "font_size" };
|
||||
|
||||
for (const std::string& option : options_to_recreate_GUI) {
|
||||
@ -761,12 +759,12 @@ void PreferencesDialog::accept(wxEvent&)
|
||||
wxGetApp().force_menu_update();
|
||||
#endif //_MSW_DARK_MODE
|
||||
#endif // _WIN32
|
||||
|
||||
|
||||
if (m_values.find("no_templates") != m_values.end())
|
||||
wxGetApp().plater()->force_filament_cb_update();
|
||||
|
||||
wxGetApp().update_ui_from_settings();
|
||||
clear_cache();
|
||||
|
||||
if (update_filament_sidebar)
|
||||
wxGetApp().plater()->sidebar().update_presets(Preset::Type::TYPE_FILAMENT);
|
||||
}
|
||||
|
||||
void PreferencesDialog::revert(wxEvent&)
|
||||
|
@ -585,10 +585,10 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset
|
||||
if (m_type == Preset::TYPE_FILAMENT)
|
||||
{
|
||||
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) {
|
||||
const Preset* selected_preset = m_collection->find_preset(m_preset_bundle->filament_presets[m_extruder_idx]);
|
||||
const Filament* selected_filament = m_preset_bundle->extruders_filaments[m_extruder_idx].get_selected_filament();
|
||||
// Wide icons are shown if the currently selected preset is not compatible with the current printer,
|
||||
// and red flag is drown in front of the selected preset.
|
||||
bool wide_icons = selected_preset && !selected_preset->is_compatible;
|
||||
const bool wide_icons = selected_filament && !selected_filament->is_compatible;
|
||||
float scale = m_em_unit*0.1f;
|
||||
|
||||
int shifl_Left = wide_icons ? int(scale * 16 + 0.5) : 0;
|
||||
@ -686,22 +686,16 @@ void PlaterPresetComboBox::switch_to_tab()
|
||||
|
||||
if (int page_id = wxGetApp().tab_panel()->FindPage(tab); page_id != wxNOT_FOUND)
|
||||
{
|
||||
//In a case of a multi-material printing, for editing another Filament Preset
|
||||
//it's needed to select this preset for the "Filament settings" Tab
|
||||
if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1 &&
|
||||
!dynamic_cast<TabFilament*>(wxGetApp().get_tab(m_type))->set_active_extruder(m_extruder_idx))
|
||||
// do nothing, if we can't set new extruder and select new preset
|
||||
return;
|
||||
|
||||
wxGetApp().tab_panel()->SetSelection(page_id);
|
||||
// Switch to Settings NotePad
|
||||
wxGetApp().mainframe->select_tab();
|
||||
|
||||
//In a case of a multi-material printing, for editing another Filament Preset
|
||||
//it's needed to select this preset for the "Filament settings" Tab
|
||||
if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1)
|
||||
{
|
||||
const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data();
|
||||
// Call select_preset() only if there is new preset and not just modified
|
||||
if (!boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()))
|
||||
{
|
||||
const std::string& preset_name = wxGetApp().preset_bundle->filaments.get_preset_name_by_alias(selected_preset);
|
||||
wxGetApp().get_tab(m_type)->select_preset(preset_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -808,7 +802,7 @@ void PlaterPresetComboBox::update()
|
||||
{
|
||||
if (m_type == Preset::TYPE_FILAMENT &&
|
||||
(m_preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA ||
|
||||
m_preset_bundle->filament_presets.size() <= (size_t)m_extruder_idx) )
|
||||
m_preset_bundle->extruders_filaments.size() <= (size_t)m_extruder_idx) )
|
||||
return;
|
||||
|
||||
// Otherwise fill in the list from scratch.
|
||||
@ -816,6 +810,8 @@ void PlaterPresetComboBox::update()
|
||||
this->Clear();
|
||||
invalidate_selection();
|
||||
|
||||
const ExtruderFilaments& extruder_filaments = m_preset_bundle->extruders_filaments[m_extruder_idx >= 0 ? m_extruder_idx : 0];
|
||||
|
||||
const Preset* selected_filament_preset = nullptr;
|
||||
std::string extruder_color;
|
||||
if (m_type == Preset::TYPE_FILAMENT) {
|
||||
@ -823,21 +819,23 @@ void PlaterPresetComboBox::update()
|
||||
if (!can_decode_color(extruder_color))
|
||||
// Extruder color is not defined.
|
||||
extruder_color.clear();
|
||||
selected_filament_preset = m_collection->find_preset(m_preset_bundle->filament_presets[m_extruder_idx]);
|
||||
selected_filament_preset = extruder_filaments.get_selected_preset();
|
||||
assert(selected_filament_preset);
|
||||
}
|
||||
|
||||
bool has_selection = m_collection->get_selected_idx() != size_t(-1);
|
||||
const Preset* selected_preset = m_type == Preset::TYPE_FILAMENT ? selected_filament_preset : has_selection ? &m_collection->get_selected_preset() : nullptr;
|
||||
// Show wide icons if the currently selected preset is not compatible with the current printer,
|
||||
// and draw a red flag in front of the selected preset.
|
||||
bool wide_icons = selected_preset && !selected_preset->is_compatible;
|
||||
bool wide_icons = m_type == Preset::TYPE_FILAMENT ?
|
||||
extruder_filaments.get_selected_filament() && !extruder_filaments.get_selected_filament()->is_compatible :
|
||||
m_collection->get_selected_idx() != size_t(-1) && !m_collection->get_selected_preset().is_compatible;
|
||||
|
||||
null_icon_width = (wide_icons ? 3 : 2) * norm_icon_width + thin_space_icon_width + wide_space_icon_width;
|
||||
|
||||
std::map<wxString, wxBitmapBundle*> nonsys_presets;
|
||||
std::map<wxString, wxBitmapBundle*> template_presets;
|
||||
|
||||
const bool allow_templates = !wxGetApp().app_config->get_bool("no_templates");
|
||||
|
||||
wxString selected_user_preset;
|
||||
wxString tooltip;
|
||||
const std::deque<Preset>& presets = m_collection->get_presets();
|
||||
@ -848,13 +846,15 @@ void PlaterPresetComboBox::update()
|
||||
for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i)
|
||||
{
|
||||
const Preset& preset = presets[i];
|
||||
bool is_selected = m_type == Preset::TYPE_FILAMENT ?
|
||||
m_preset_bundle->filament_presets[m_extruder_idx] == preset.name :
|
||||
const bool is_selected = m_type == Preset::TYPE_FILAMENT ?
|
||||
selected_filament_preset->name == preset.name :
|
||||
// The case, when some physical printer is selected
|
||||
m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection() ? false :
|
||||
i == m_collection->get_selected_idx();
|
||||
|
||||
if (!preset.is_visible || (!preset.is_compatible && !is_selected))
|
||||
const bool is_compatible = m_type == Preset::TYPE_FILAMENT ? extruder_filaments.filament(i).is_compatible : preset.is_compatible;
|
||||
|
||||
if (!preset.is_visible || (!is_compatible && !is_selected))
|
||||
continue;
|
||||
|
||||
std::string bitmap_key, filament_rgb, extruder_rgb, material_rgb;
|
||||
@ -878,17 +878,19 @@ void PlaterPresetComboBox::update()
|
||||
}
|
||||
|
||||
auto bmp = get_bmp(bitmap_key, wide_icons, bitmap_type_name,
|
||||
preset.is_compatible, preset.is_system || preset.is_default,
|
||||
is_compatible, preset.is_system || preset.is_default,
|
||||
single_bar, filament_rgb, extruder_rgb, material_rgb);
|
||||
assert(bmp);
|
||||
|
||||
const std::string name = preset.alias.empty() ? preset.name : preset.alias;
|
||||
if (preset.is_default || preset.is_system) {
|
||||
if (preset.vendor && preset.vendor->templates_profile) {
|
||||
template_presets.emplace(get_preset_name(preset), bmp);
|
||||
if (is_selected) {
|
||||
selected_user_preset = get_preset_name(preset);
|
||||
tooltip = from_u8(preset.name);
|
||||
if (allow_templates) {
|
||||
template_presets.emplace(get_preset_name(preset), bmp);
|
||||
if (is_selected) {
|
||||
selected_user_preset = get_preset_name(preset);
|
||||
tooltip = from_u8(preset.name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Append(get_preset_name(preset), *bmp);
|
||||
@ -919,8 +921,7 @@ void PlaterPresetComboBox::update()
|
||||
}
|
||||
}
|
||||
|
||||
const AppConfig* app_config = wxGetApp().app_config;
|
||||
if (!template_presets.empty() && app_config->get("no_templates") == "0") {
|
||||
if (!template_presets.empty()) {
|
||||
set_label_marker(Append(separator(L("Template presets")), wxNullBitmap));
|
||||
for (std::map<wxString, wxBitmapBundle*>::iterator it = template_presets.begin(); it != template_presets.end(); ++it) {
|
||||
Append(it->first, *it->second);
|
||||
@ -1063,15 +1064,19 @@ void TabPresetComboBox::update()
|
||||
Clear();
|
||||
invalidate_selection();
|
||||
|
||||
const ExtruderFilaments& extruder_filaments = m_preset_bundle->extruders_filaments[m_active_extruder_idx];
|
||||
|
||||
const std::deque<Preset>& presets = m_collection->get_presets();
|
||||
|
||||
std::map<wxString, std::pair<wxBitmapBundle*, bool>> nonsys_presets;
|
||||
std::map<wxString, std::pair<wxBitmapBundle*, bool>> template_presets;
|
||||
|
||||
const bool allow_templates = !wxGetApp().app_config->get_bool("no_templates");
|
||||
|
||||
wxString selected = "";
|
||||
if (!presets.front().is_visible)
|
||||
set_label_marker(Append(separator(L("System presets")), NullBitmapBndl()));
|
||||
size_t idx_selected = m_collection->get_selected_idx();
|
||||
size_t idx_selected = m_type == Preset::TYPE_FILAMENT ? extruder_filaments.get_selected_idx() : m_collection->get_selected_idx();
|
||||
|
||||
if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) {
|
||||
std::string sel_preset_name = m_preset_bundle->physical_printers.get_selected_printer_preset_name();
|
||||
@ -1083,7 +1088,10 @@ void TabPresetComboBox::update()
|
||||
for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i)
|
||||
{
|
||||
const Preset& preset = presets[i];
|
||||
if (!preset.is_visible || (!show_incompatible && !preset.is_compatible && i != idx_selected))
|
||||
|
||||
const bool is_compatible = m_type == Preset::TYPE_FILAMENT ? extruder_filaments.filament(i).is_compatible : preset.is_compatible;
|
||||
|
||||
if (!preset.is_visible || (!show_incompatible && !is_compatible && i != idx_selected))
|
||||
continue;
|
||||
|
||||
// marker used for disable incompatible printer models for the selected physical printer
|
||||
@ -1097,14 +1105,16 @@ void TabPresetComboBox::update()
|
||||
}
|
||||
std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name;
|
||||
|
||||
auto bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default);
|
||||
auto bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, is_compatible, preset.is_system || preset.is_default);
|
||||
assert(bmp);
|
||||
|
||||
if (preset.is_default || preset.is_system) {
|
||||
if (preset.is_default || preset.is_system) {
|
||||
if (preset.vendor && preset.vendor->templates_profile) {
|
||||
template_presets.emplace(get_preset_name(preset), std::pair<wxBitmapBundle*, bool>(bmp, is_enabled));
|
||||
if (i == idx_selected)
|
||||
selected = get_preset_name(preset);
|
||||
if (allow_templates) {
|
||||
template_presets.emplace(get_preset_name(preset), std::pair<wxBitmapBundle*, bool>(bmp, is_enabled));
|
||||
if (i == idx_selected)
|
||||
selected = get_preset_name(preset);
|
||||
}
|
||||
} else {
|
||||
int item_id = Append(get_preset_name(preset), *bmp);
|
||||
if (!is_enabled)
|
||||
@ -1136,9 +1146,8 @@ void TabPresetComboBox::update()
|
||||
validate_selection(it->first == selected);
|
||||
}
|
||||
}
|
||||
|
||||
const AppConfig* app_config = wxGetApp().app_config;
|
||||
if (!template_presets.empty() && app_config->get("no_templates") == "0") {
|
||||
|
||||
if (!template_presets.empty()) {
|
||||
set_label_marker(Append(separator(L("Template presets")), wxNullBitmap));
|
||||
for (std::map<wxString, std::pair<wxBitmapBundle*, bool>>::iterator it = template_presets.begin(); it != template_presets.end(); ++it) {
|
||||
int item_id = Append(it->first, *it->second.first);
|
||||
|
@ -179,6 +179,8 @@ class TabPresetComboBox : public PresetComboBox
|
||||
{
|
||||
bool show_incompatible {false};
|
||||
bool m_enable_all {false};
|
||||
// This parameter is used by FilamentSettings tab to show filament setting related to the active extruder
|
||||
int m_active_extruder_idx {0};
|
||||
|
||||
public:
|
||||
TabPresetComboBox(wxWindow *parent, Preset::Type preset_type);
|
||||
@ -197,6 +199,9 @@ public:
|
||||
|
||||
PresetCollection* presets() const { return m_collection; }
|
||||
Preset::Type type() const { return m_type; }
|
||||
|
||||
// used by Filaments tab to update preset list according to the particular extruder
|
||||
void set_active_extruder(int extruder_idx) { m_active_extruder_idx = extruder_idx; }
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
|
@ -75,9 +75,9 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
|
||||
{
|
||||
// Find out, to which nozzle index is the current filament profile assigned.
|
||||
int idx_extruder = 0;
|
||||
int num_extruders = (int)preset_bundle.filament_presets.size();
|
||||
int num_extruders = (int)preset_bundle.extruders_filaments.size();
|
||||
for (; idx_extruder < num_extruders; ++ idx_extruder)
|
||||
if (preset_bundle.filament_presets[idx_extruder] == preset_bundle.filaments.get_selected_preset_name())
|
||||
if (preset_bundle.extruders_filaments[idx_extruder].get_selected_preset_name() == preset_bundle.filaments.get_selected_preset_name())
|
||||
break;
|
||||
if (idx_extruder == num_extruders)
|
||||
// The current filament preset is not active for any extruder.
|
||||
|
@ -858,6 +858,43 @@ std::pair<BoundingBoxf3, Transform3d> Selection::get_bounding_box_in_reference_s
|
||||
return { out_box, out_trafo.get_matrix_no_scaling_factor() };
|
||||
}
|
||||
|
||||
BoundingBoxf Selection::get_screen_space_bounding_box()
|
||||
{
|
||||
BoundingBoxf ss_box;
|
||||
if (!is_empty()) {
|
||||
const auto& [box, box_trafo] = get_bounding_box_in_current_reference_system();
|
||||
|
||||
// vertices
|
||||
std::vector<Vec3d> vertices = {
|
||||
{ box.min.x(), box.min.y(), box.min.z() },
|
||||
{ box.max.x(), box.min.y(), box.min.z() },
|
||||
{ box.max.x(), box.max.y(), box.min.z() },
|
||||
{ box.min.x(), box.max.y(), box.min.z() },
|
||||
{ box.min.x(), box.min.y(), box.max.z() },
|
||||
{ box.max.x(), box.min.y(), box.max.z() },
|
||||
{ box.max.x(), box.max.y(), box.max.z() },
|
||||
{ box.min.x(), box.max.y(), box.max.z() }
|
||||
};
|
||||
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
const Matrix4d projection_view_matrix = camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix();
|
||||
const std::array<int, 4>& viewport = camera.get_viewport();
|
||||
|
||||
const double half_w = 0.5 * double(viewport[2]);
|
||||
const double h = double(viewport[3]);
|
||||
const double half_h = 0.5 * h;
|
||||
for (const Vec3d& v : vertices) {
|
||||
const Vec3d world = box_trafo * v;
|
||||
const Vec4d clip = projection_view_matrix * Vec4d(world.x(), world.y(), world.z(), 1.0);
|
||||
const Vec3d ndc = Vec3d(clip.x(), clip.y(), clip.z()) / clip.w();
|
||||
const Vec2d ss = Vec2d(half_w * ndc.x() + double(viewport[0]) + half_w, h - (half_h * ndc.y() + double(viewport[1]) + half_h));
|
||||
ss_box.merge(ss);
|
||||
}
|
||||
}
|
||||
|
||||
return ss_box;
|
||||
}
|
||||
|
||||
void Selection::setup_cache()
|
||||
{
|
||||
if (!m_valid)
|
||||
@ -1717,8 +1754,7 @@ std::vector<unsigned int> Selection::get_volume_idxs_from_volume(unsigned int ob
|
||||
{
|
||||
std::vector<unsigned int> idxs;
|
||||
|
||||
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i)
|
||||
{
|
||||
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) {
|
||||
const GLVolume* v = (*m_volumes)[i];
|
||||
if (v->object_idx() == (int)object_idx && v->volume_idx() == (int)volume_idx) {
|
||||
if ((int)instance_idx != -1 && v->instance_idx() == (int)instance_idx)
|
||||
|
@ -319,6 +319,9 @@ public:
|
||||
// and the transform to place and orient it in world coordinates
|
||||
std::pair<BoundingBoxf3, Transform3d> get_bounding_box_in_reference_system(ECoordinatesType type) const;
|
||||
|
||||
// Returns the screen space bounding box
|
||||
BoundingBoxf get_screen_space_bounding_box();
|
||||
|
||||
void setup_cache();
|
||||
|
||||
void translate(const Vec3d& displacement, TransformationType transformation_type);
|
||||
|
@ -1236,7 +1236,8 @@ void Tab::on_presets_changed()
|
||||
m_dependent_tabs.clear();
|
||||
|
||||
// Update Project dirty state, update application title bar.
|
||||
wxGetApp().plater()->update_project_dirty_from_presets();
|
||||
if (wxGetApp().mainframe)
|
||||
wxGetApp().plater()->update_project_dirty_from_presets();
|
||||
}
|
||||
|
||||
void Tab::build_preset_description_line(ConfigOptionsGroup* optgroup)
|
||||
@ -1933,8 +1934,64 @@ void TabFilament::update_filament_overrides_page()
|
||||
}
|
||||
}
|
||||
|
||||
void TabFilament::create_extruder_combobox()
|
||||
{
|
||||
m_extruders_cb = new BitmapComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(12 * m_em_unit, -1), 0, nullptr, wxCB_READONLY);
|
||||
m_extruders_cb->Hide();
|
||||
|
||||
m_extruders_cb->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent&) {
|
||||
set_active_extruder(m_extruders_cb->GetSelection());
|
||||
});
|
||||
|
||||
m_h_buttons_sizer->AddSpacer(3*em_unit(this));
|
||||
m_h_buttons_sizer->Add(m_extruders_cb, 0, wxALIGN_CENTER_VERTICAL);
|
||||
}
|
||||
|
||||
void TabFilament::update_extruder_combobox()
|
||||
{
|
||||
const size_t extruder_cnt = static_cast<const ConfigOptionFloats*>(m_preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter"))->values.size();
|
||||
|
||||
m_extruders_cb->Show(extruder_cnt > 1);
|
||||
|
||||
if (extruder_cnt != m_extruders_cb->GetCount()) {
|
||||
m_extruders_cb->Clear();
|
||||
for (size_t id = 1; id <= extruder_cnt; id++)
|
||||
m_extruders_cb->Append(format_wxstr("%1% %2%", _L("Extruder"), id), *get_bmp_bundle("funnel"));
|
||||
}
|
||||
|
||||
if (m_active_extruder >= int(extruder_cnt))
|
||||
m_active_extruder = 0;
|
||||
|
||||
m_extruders_cb->SetSelection(m_active_extruder);
|
||||
}
|
||||
|
||||
bool TabFilament::set_active_extruder(int new_selected_extruder)
|
||||
{
|
||||
if (m_active_extruder == new_selected_extruder)
|
||||
return true;
|
||||
|
||||
const int old_extruder_id = m_active_extruder;
|
||||
m_active_extruder = new_selected_extruder;
|
||||
m_presets_choice->set_active_extruder(m_active_extruder);
|
||||
|
||||
if (!select_preset(m_preset_bundle->extruders_filaments[m_active_extruder].get_selected_preset_name())) {
|
||||
m_active_extruder = old_extruder_id;
|
||||
m_presets_choice->set_active_extruder(m_active_extruder);
|
||||
m_extruders_cb->SetSelection(m_active_extruder);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_active_extruder != m_extruders_cb->GetSelection())
|
||||
m_extruders_cb->Select(m_active_extruder);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TabFilament::build()
|
||||
{
|
||||
// add extruder combobox
|
||||
create_extruder_combobox();
|
||||
|
||||
m_presets = &m_preset_bundle->filaments;
|
||||
load_initial_data();
|
||||
|
||||
@ -2189,7 +2246,7 @@ void TabFilament::update()
|
||||
|
||||
m_update_cnt--;
|
||||
|
||||
if (m_update_cnt == 0)
|
||||
if (m_update_cnt == 0 && wxGetApp().mainframe)
|
||||
wxGetApp().mainframe->on_config_changed(m_config);
|
||||
}
|
||||
|
||||
@ -2210,6 +2267,44 @@ void TabFilament::msw_rescale()
|
||||
Tab::msw_rescale();
|
||||
}
|
||||
|
||||
void TabFilament::load_current_preset()
|
||||
{
|
||||
assert(m_active_extruder >= 0 && m_active_extruder < m_preset_bundle->extruders_filaments.size());
|
||||
const std::string& selected_extr_filament_name = m_preset_bundle->extruders_filaments[m_active_extruder].get_selected_preset_name();
|
||||
const std::string& selected_filament_name = m_presets->get_selected_preset_name();
|
||||
if (selected_extr_filament_name != selected_filament_name)
|
||||
m_presets->select_preset_by_name(selected_extr_filament_name, false);
|
||||
|
||||
Tab::load_current_preset();
|
||||
}
|
||||
|
||||
bool TabFilament::select_preset_by_name(const std::string &name_w_suffix, bool force)
|
||||
{
|
||||
const bool is_selected_filament = Tab::select_preset_by_name(name_w_suffix, force);
|
||||
const bool is_selected_extr_filament = m_preset_bundle->extruders_filaments[m_active_extruder].select_filament(name_w_suffix, force);
|
||||
return is_selected_filament && is_selected_extr_filament;
|
||||
}
|
||||
|
||||
bool TabFilament::save_current_preset(const std::string &new_name, bool detach)
|
||||
{
|
||||
m_preset_bundle->cache_extruder_filaments_names();
|
||||
const bool is_saved = Tab::save_current_preset(new_name, detach);
|
||||
if (is_saved) {
|
||||
m_preset_bundle->reset_extruder_filaments();
|
||||
m_preset_bundle->extruders_filaments[m_active_extruder].select_filament(m_presets->get_idx_selected());
|
||||
}
|
||||
return is_saved;
|
||||
}
|
||||
|
||||
bool TabFilament::delete_current_preset()
|
||||
{
|
||||
m_preset_bundle->cache_extruder_filaments_names();
|
||||
const bool is_deleted = Tab::delete_current_preset();
|
||||
if (is_deleted)
|
||||
m_preset_bundle->reset_extruder_filaments();
|
||||
return is_deleted;
|
||||
}
|
||||
|
||||
wxSizer* Tab::description_line_widget(wxWindow* parent, ogStaticText* *StaticText, wxString text /*= wxEmptyString*/)
|
||||
{
|
||||
*StaticText = new ogStaticText(parent, text);
|
||||
@ -2339,7 +2434,6 @@ void TabPrinter::build_fff()
|
||||
const wxString msg_text = _(L("Single Extruder Multi Material is selected, \n"
|
||||
"and all extruders must have the same diameter.\n"
|
||||
"Do you want to change the diameter for all extruders to first extruder nozzle diameter value?"));
|
||||
//wxMessageDialog dialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO);
|
||||
MessageDialog dialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO);
|
||||
|
||||
DynamicPrintConfig new_conf = *m_config;
|
||||
@ -2357,6 +2451,14 @@ void TabPrinter::build_fff()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never);
|
||||
// Upadte related comboboxes on Sidebar and Tabs
|
||||
Sidebar& sidebar = wxGetApp().plater()->sidebar();
|
||||
for (const Preset::Type& type : {Preset::TYPE_PRINT, Preset::TYPE_FILAMENT}) {
|
||||
sidebar.update_presets(type);
|
||||
wxGetApp().get_tab(type)->update_tab_ui();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -2625,15 +2727,21 @@ void TabPrinter::build_sla()
|
||||
void TabPrinter::extruders_count_changed(size_t extruders_count)
|
||||
{
|
||||
bool is_count_changed = false;
|
||||
bool is_updated_mm_filament_presets = false;
|
||||
if (m_extruders_count != extruders_count) {
|
||||
m_extruders_count = extruders_count;
|
||||
m_preset_bundle->printers.get_edited_preset().set_num_extruders(extruders_count);
|
||||
m_preset_bundle->update_multi_material_filament_presets();
|
||||
is_count_changed = true;
|
||||
is_count_changed = is_updated_mm_filament_presets = true;
|
||||
}
|
||||
else if (m_extruders_count == 1 &&
|
||||
m_preset_bundle->project_config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values.size()>1)
|
||||
m_preset_bundle->project_config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values.size()>1) {
|
||||
is_updated_mm_filament_presets = true;
|
||||
}
|
||||
|
||||
if (is_updated_mm_filament_presets) {
|
||||
m_preset_bundle->update_multi_material_filament_presets();
|
||||
m_preset_bundle->update_filaments_compatible(PresetSelectCompatibleType::OnlyIfWasCompatible);
|
||||
}
|
||||
|
||||
/* This function should be call in any case because of correct updating/rebuilding
|
||||
* of unregular pages of a Printer Settings
|
||||
@ -2768,7 +2876,10 @@ void TabPrinter::build_extruder_pages(size_t n_before_extruders)
|
||||
|
||||
optgroup->m_on_change = [this, extruder_idx](const t_config_option_key&opt_key, boost::any value)
|
||||
{
|
||||
if (m_config->opt_bool("single_extruder_multi_material") && m_extruders_count > 1 && opt_key.find_first_of("nozzle_diameter") != std::string::npos)
|
||||
const bool is_single_extruder_MM = m_config->opt_bool("single_extruder_multi_material");
|
||||
const bool is_nozzle_diameter_changed = opt_key.find_first_of("nozzle_diameter") != std::string::npos;
|
||||
|
||||
if (is_single_extruder_MM && m_extruders_count > 1 && is_nozzle_diameter_changed)
|
||||
{
|
||||
SuppressBackgroundProcessingUpdate sbpu;
|
||||
const double new_nd = boost::any_cast<double>(value);
|
||||
@ -2798,6 +2909,15 @@ void TabPrinter::build_extruder_pages(size_t n_before_extruders)
|
||||
}
|
||||
}
|
||||
|
||||
if (is_nozzle_diameter_changed) {
|
||||
if (extruder_idx == 0)
|
||||
// Mark the print & filament enabled if they are compatible with the currently selected preset.
|
||||
// If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible.
|
||||
m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never);
|
||||
else
|
||||
m_preset_bundle->update_filaments_compatible(PresetSelectCompatibleType::Never, extruder_idx);
|
||||
}
|
||||
|
||||
update_dirty();
|
||||
update();
|
||||
};
|
||||
@ -3040,6 +3160,7 @@ void TabPrinter::update_pages()
|
||||
if (m_extruders_count > 1)
|
||||
{
|
||||
m_preset_bundle->update_multi_material_filament_presets();
|
||||
m_preset_bundle->update_filaments_compatible(PresetSelectCompatibleType::OnlyIfWasCompatible);
|
||||
on_value_change("extruders_count", m_extruders_count);
|
||||
}
|
||||
}
|
||||
@ -3392,7 +3513,7 @@ void Tab::update_preset_choice()
|
||||
// Called by the UI combo box when the user switches profiles, and also to delete the current profile.
|
||||
// Select a preset by a name.If !defined(name), then the default preset is selected.
|
||||
// If the current profile is modified, user is asked to save the changes.
|
||||
void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, const std::string& last_selected_ph_printer_name/* =""*/)
|
||||
bool Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, const std::string& last_selected_ph_printer_name/* =""*/)
|
||||
{
|
||||
if (preset_name.empty()) {
|
||||
if (delete_current) {
|
||||
@ -3492,7 +3613,8 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
|
||||
// It does not matter which preset will be made active as the preset will be re-selected from the preset_name variable.
|
||||
// The 'external' presets will only be removed from the preset list, their files will not be deleted.
|
||||
try {
|
||||
m_presets->delete_current_preset();
|
||||
// cache previously selected names
|
||||
delete_current_preset();
|
||||
} catch (const std::exception & /* e */) {
|
||||
//FIXME add some error reporting!
|
||||
canceled = true;
|
||||
@ -3513,7 +3635,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
|
||||
}
|
||||
}
|
||||
|
||||
update_tab_ui();
|
||||
// update_tab_ui(); //! ysFIXME delete after testing
|
||||
|
||||
// Trigger the on_presets_changed event so that we also restore the previous value in the plater selector,
|
||||
// if this action was initiated from the plater.
|
||||
@ -3522,7 +3644,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
|
||||
if (current_dirty)
|
||||
m_presets->discard_current_changes();
|
||||
|
||||
const bool is_selected = m_presets->select_preset_by_name(preset_name, false) || delete_current;
|
||||
const bool is_selected = select_preset_by_name(preset_name, false) || delete_current;
|
||||
assert(m_presets->get_edited_preset().name == preset_name || ! is_selected);
|
||||
// Mark the print & filament enabled if they are compatible with the currently selected preset.
|
||||
// The following method should not discard changes of current print or filament presets on change of a printer profile,
|
||||
@ -3565,6 +3687,8 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
|
||||
|
||||
if (technology_changed)
|
||||
wxGetApp().mainframe->technology_changed();
|
||||
|
||||
return !canceled;
|
||||
}
|
||||
|
||||
// If the current preset is dirty, the user is asked whether the changes may be discarded.
|
||||
@ -3812,16 +3936,12 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach)
|
||||
// focus currently.is there anything better than this ?
|
||||
//! m_treectrl->OnSetFocus();
|
||||
|
||||
auto& old_preset = m_presets->get_edited_preset();
|
||||
Preset& edited_preset = m_presets->get_edited_preset();
|
||||
bool from_template = false;
|
||||
std::string edited_printer;
|
||||
if (m_type == Preset::TYPE_FILAMENT && old_preset.vendor && old_preset.vendor->templates_profile)
|
||||
{
|
||||
//TODO: is this really the best way to get "printer_model" option of currently edited printer?
|
||||
edited_printer = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt<ConfigOptionString>("printer_model")->serialize();
|
||||
if (!edited_printer.empty())
|
||||
from_template = true;
|
||||
|
||||
if (m_type == Preset::TYPE_FILAMENT && edited_preset.vendor && edited_preset.vendor->templates_profile) {
|
||||
edited_printer = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_string("printer_model");
|
||||
from_template = !edited_printer.empty();
|
||||
}
|
||||
|
||||
if (name.empty()) {
|
||||
@ -3836,24 +3956,21 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach)
|
||||
if (detach && m_type == Preset::TYPE_PRINTER)
|
||||
m_config->opt_string("printer_model", true) = "";
|
||||
|
||||
// Update compatible printers
|
||||
if (from_template && !edited_printer.empty()) {
|
||||
std::string cond = edited_preset.compatible_printers_condition();
|
||||
if (!cond.empty())
|
||||
cond += " and ";
|
||||
cond += "printer_model == \"" + edited_printer + "\"";
|
||||
edited_preset.config.opt_string("compatible_printers_condition") = cond;
|
||||
}
|
||||
|
||||
// Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini
|
||||
m_presets->save_current_preset(name, detach);
|
||||
save_current_preset(name, detach);
|
||||
|
||||
if (detach && m_type == Preset::TYPE_PRINTER)
|
||||
wxGetApp().mainframe->on_config_changed(m_config);
|
||||
|
||||
// Update compatible printers
|
||||
if (from_template && !edited_printer.empty()) {
|
||||
auto& new_preset = m_presets->get_edited_preset();
|
||||
std::string cond = new_preset.compatible_printers_condition();
|
||||
if (!cond.empty())
|
||||
cond += " and ";
|
||||
cond += "printer_model == \""+edited_printer+"\"";
|
||||
new_preset.config.set("compatible_printers_condition", cond);
|
||||
new_preset.save();
|
||||
m_presets->save_current_preset(name, detach);
|
||||
load_current_preset();
|
||||
}
|
||||
|
||||
// Mark the print & filament enabled if they are compatible with the currently selected preset.
|
||||
// If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible.
|
||||
@ -3980,7 +4097,7 @@ void Tab::rename_preset()
|
||||
// sort presets after renaming
|
||||
std::sort(m_presets->begin(), m_presets->end());
|
||||
// update selection
|
||||
m_presets->select_preset_by_name(new_name, true);
|
||||
select_preset_by_name(new_name, true);
|
||||
|
||||
m_presets_choice->update();
|
||||
on_presets_changed();
|
||||
@ -4675,6 +4792,21 @@ void Tab::set_tooltips_text()
|
||||
"Click to reset current value to the last saved preset."));
|
||||
}
|
||||
|
||||
bool Tab::select_preset_by_name(const std::string &name_w_suffix, bool force)
|
||||
{
|
||||
return m_presets->select_preset_by_name(name_w_suffix, force);
|
||||
}
|
||||
|
||||
bool Tab::save_current_preset(const std::string& new_name, bool detach)
|
||||
{
|
||||
return m_presets->save_current_preset(new_name, detach);
|
||||
}
|
||||
|
||||
bool Tab::delete_current_preset()
|
||||
{
|
||||
return m_presets->delete_current_preset();
|
||||
}
|
||||
|
||||
Page::Page(wxWindow* parent, const wxString& title, int iconID) :
|
||||
m_parent(parent),
|
||||
m_title(title),
|
||||
|
@ -307,12 +307,13 @@ public:
|
||||
long style = wxBU_EXACTFIT | wxNO_BORDER);
|
||||
void add_scaled_bitmap(wxWindow* parent, ScalableBitmap& btn, const std::string& icon_name);
|
||||
void update_ui_items_related_on_parent_preset(const Preset* selected_preset_parent);
|
||||
void load_current_preset();
|
||||
virtual void load_current_preset();
|
||||
void rebuild_page_tree();
|
||||
void update_btns_enabling();
|
||||
void update_preset_choice();
|
||||
// Select a new preset, possibly delete the current one.
|
||||
void select_preset(std::string preset_name = "", bool delete_current = false, const std::string& last_selected_ph_printer_name = "");
|
||||
// return false, if action was canceled
|
||||
bool select_preset(std::string preset_name = "", bool delete_current = false, const std::string& last_selected_ph_printer_name = "");
|
||||
bool may_discard_current_dirty_preset(PresetCollection* presets = nullptr, const std::string& new_printer_name = "");
|
||||
|
||||
virtual void clear_pages();
|
||||
@ -402,6 +403,10 @@ protected:
|
||||
void fill_icon_descriptions();
|
||||
void set_tooltips_text();
|
||||
|
||||
virtual bool select_preset_by_name(const std::string& name_w_suffix, bool force);
|
||||
virtual bool save_current_preset(const std::string& new_name, bool detach);
|
||||
virtual bool delete_current_preset();
|
||||
|
||||
ConfigManipulation m_config_manipulation;
|
||||
ConfigManipulation get_config_manipulation();
|
||||
};
|
||||
@ -432,7 +437,8 @@ private:
|
||||
|
||||
class TabFilament : public Tab
|
||||
{
|
||||
private:
|
||||
BitmapComboBox* m_extruders_cb {nullptr};
|
||||
int m_active_extruder {0};
|
||||
ogStaticText* m_volumetric_speed_description_line {nullptr};
|
||||
ogStaticText* m_cooling_description_line {nullptr};
|
||||
|
||||
@ -440,6 +446,7 @@ private:
|
||||
void update_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string &opt_key, int opt_index = 0, bool is_checked = true);
|
||||
void add_filament_overrides_page();
|
||||
void update_filament_overrides_page();
|
||||
void create_extruder_combobox();
|
||||
void update_volumetric_flow_preset_hints();
|
||||
|
||||
std::map<std::string, wxCheckBox*> m_overrides_options;
|
||||
@ -455,6 +462,18 @@ public:
|
||||
void clear_pages() override;
|
||||
void msw_rescale() override;
|
||||
bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptFFF; }
|
||||
void load_current_preset() override;
|
||||
|
||||
// set actiev extruder and update preset combobox if needed
|
||||
// return false, if new preset wasn't selected
|
||||
bool set_active_extruder(int new_selected_extruder);
|
||||
void update_extruder_combobox();
|
||||
int get_active_extruder() const { return m_active_extruder; }
|
||||
|
||||
protected:
|
||||
bool select_preset_by_name(const std::string& name_w_suffix, bool force) override;
|
||||
bool save_current_preset(const std::string& new_name, bool detach) override;
|
||||
bool delete_current_preset() override;
|
||||
};
|
||||
|
||||
class TabPrinter : public Tab
|
||||
|
@ -260,7 +260,7 @@ SCENARIO("Cooling integration tests", "[Cooling]") {
|
||||
});
|
||||
THEN("slowdown_below_layer_time is honored") {
|
||||
// Account for some inaccuracies.
|
||||
const double slowdown_below_layer_time = config.opt<ConfigOptionInts>("slowdown_below_layer_time")->values.front() - 0.2;
|
||||
const double slowdown_below_layer_time = config.opt<ConfigOptionInts>("slowdown_below_layer_time")->values.front() - 0.5;
|
||||
size_t minimum_time_honored = std::count_if(layer_times.begin(), layer_times.end(),
|
||||
[slowdown_below_layer_time](double t){ return t > slowdown_below_layer_time; });
|
||||
REQUIRE(minimum_time_honored == layer_times.size());
|
||||
|
@ -48,9 +48,11 @@ SCENARIO("Shells", "[Shells]") {
|
||||
REQUIRE(! has_shells(i));
|
||||
}
|
||||
THEN("correct number of top solid layers") {
|
||||
for (int i = 0; i < top_solid_layers; ++ i)
|
||||
// NOTE: there is one additional layer with enusring line under the bridge layer, bridges would be otherwise anchored weakly to the perimeter.
|
||||
size_t additional_ensuring_anchors = top_solid_layers > 0 ? 1 : 0;
|
||||
for (int i = 0; i < top_solid_layers + additional_ensuring_anchors; ++ i)
|
||||
REQUIRE(has_shells(int(zs.size()) - i - 1));
|
||||
for (int i = top_solid_layers; i < int(zs.size() / 2); ++ i)
|
||||
for (int i = top_solid_layers + additional_ensuring_anchors; i < int(zs.size() / 2); ++ i)
|
||||
REQUIRE(! has_shells(int(zs.size()) - i - 1));
|
||||
}
|
||||
if (top_solid_layers > 0) {
|
||||
@ -144,7 +146,7 @@ SCENARIO("Shells (from Perl)", "[Shells]") {
|
||||
for (auto z : layers_with_speed(Slic3r::Test::slice({TestMesh::V}, config), solid_speed))
|
||||
if (z <= 7.2)
|
||||
++ n;
|
||||
REQUIRE(n == 3);
|
||||
REQUIRE(n == 3 + 1/*one additional layer with ensuring for bridge anchors*/);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user