Arrange: Use arrange for multiple beds.

- Support multiple wipe towers
- Arrange to grid directly within the arrange algorithm
- Suporrt arranging just the current bed
- Support fill bed with instances
- Add arrange selected on current bed
- Add arrange current bed keyboard shortcuts
- Fix cut not arranging properly (set instance bed after cut to the active bed, use arrange current bed selection only after cut)
- Fix shift-D on arrange
- Add window with options to the bed arrange button
This commit is contained in:
Martin Šach 2024-11-01 16:37:22 +01:00 committed by Lukas Matena
parent 5b59785456
commit 3e33631abf
46 changed files with 932 additions and 347 deletions

View File

@ -1,23 +1,87 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve"> <svg
<g id="arrange"> version="1.0"
<g> id="Layer_1"
<path fill="#FFFFFF" d="M120,122.5H8c-1.38,0-2.5-1.12-2.5-2.5V8c0-1.38,1.12-2.5,2.5-2.5h112c1.38,0,2.5,1.12,2.5,2.5v112 x="0px"
C122.5,121.38,121.38,122.5,120,122.5z M10.5,117.5h107v-107h-107V117.5z"/> y="0px"
</g> viewBox="0 0 128 128"
<g> enable-background="new 0 0 128 128"
<path fill="#ED6B21" d="M104,58.5H24c-1.38,0-2.5-1.12-2.5-2.5V24c0-1.38,1.12-2.5,2.5-2.5h80c1.38,0,2.5,1.12,2.5,2.5v32 xml:space="preserve"
C106.5,57.38,105.38,58.5,104,58.5z M26.5,53.5h75v-27h-75V53.5z"/> sodipodi:docname="arrange_current.svg"
</g> inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
<g> xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
<path fill="#ED6B21" d="M48,106.5H24c-1.38,0-2.5-1.12-2.5-2.5V72c0-1.38,1.12-2.5,2.5-2.5h24c1.38,0,2.5,1.12,2.5,2.5v32 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
C50.5,105.38,49.38,106.5,48,106.5z M26.5,101.5h19v-27h-19V101.5z"/> xmlns="http://www.w3.org/2000/svg"
</g> xmlns:svg="http://www.w3.org/2000/svg"><defs
<g> id="defs943" /><sodipodi:namedview
<path fill="#ED6B21" d="M104,106.5H64c-1.38,0-2.5-1.12-2.5-2.5V72c0-1.38,1.12-2.5,2.5-2.5h40c1.38,0,2.5,1.12,2.5,2.5v32 id="namedview941"
C106.5,105.38,105.38,106.5,104,106.5z M66.5,101.5h35v-27h-35V101.5z"/> pagecolor="#ffffff"
</g> bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="2.8085938"
inkscape:cx="6.5869263"
inkscape:cy="71.744089"
inkscape:window-width="1916"
inkscape:window-height="1032"
inkscape:window-x="0"
inkscape:window-y="46"
inkscape:window-maximized="1"
inkscape:current-layer="arrange" />
<g
id="arrange">
<g
id="g933"
transform="translate(-16,16)">
<path
fill="#ed6b21"
d="M 54,106.5 H 24 c -1.38,0 -2.5,-1.12 -2.5,-2.5 V 74 c 0,-1.38 1.12,-2.5 2.5,-2.5 h 30 c 1.38,0 2.5,1.12 2.5,2.5 v 30 c 0,1.38 -1.12,2.5 -2.5,2.5 z m -27.5,-5 h 25 v -25 h -25 z"
id="path931"
sodipodi:nodetypes="sssssssssccccc" />
</g><g
id="g933-9"
transform="translate(-16,-25)"><path
fill="#ed6b21"
d="M 54,106.5 H 24 c -1.38,0 -2.5,-1.12 -2.5,-2.5 V 74 c 0,-1.38 1.12,-2.5 2.5,-2.5 h 30 c 1.38,0 2.5,1.12 2.5,2.5 v 30 c 0,1.38 -1.12,2.5 -2.5,2.5 z m -27.5,-5 h 25 v -25 h -25 z"
id="path931-1"
sodipodi:nodetypes="sssssssssccccc" /></g><g
id="g933-9-2"
transform="translate(-16,-66)"><path
fill="#ed6b21"
d="M 54,106.5 H 24 c -1.38,0 -2.5,-1.12 -2.5,-2.5 V 74 c 0,-1.38 1.12,-2.5 2.5,-2.5 h 30 c 1.38,0 2.5,1.12 2.5,2.5 v 30 c 0,1.38 -1.12,2.5 -2.5,2.5 z m -27.5,-5 h 25 v -25 h -25 z"
id="path931-1-7"
sodipodi:nodetypes="sssssssssccccc" /></g><g
id="g933-9-2-3"
transform="translate(66,-25)"><path
fill="#ed6b21"
d="M 54,106.5 H 24 c -1.38,0 -2.5,-1.12 -2.5,-2.5 V 74 c 0,-1.38 1.12,-2.5 2.5,-2.5 h 30 c 1.38,0 2.5,1.12 2.5,2.5 v 30 c 0,1.38 -1.12,2.5 -2.5,2.5 z m -27.5,-5 h 25 v -25 h -25 z"
id="path931-1-7-6"
sodipodi:nodetypes="sssssssssccccc" /></g><g
id="g933-9-2-0"
transform="translate(25,-25)"><path
fill="#ed6b21"
d="M 54,106.5 H 24 c -1.38,0 -2.5,-1.12 -2.5,-2.5 V 74 c 0,-1.38 1.12,-2.5 2.5,-2.5 h 30 c 1.38,0 2.5,1.12 2.5,2.5 v 30 c 0,1.38 -1.12,2.5 -2.5,2.5 z m -27.5,-5 h 25 v -25 h -25 z"
id="path931-1-7-9"
sodipodi:nodetypes="sssssssssccccc" /></g><g
id="g933-3"
transform="translate(25,16)"><path
fill="#ed6b21"
d="M 54,106.5 H 24 c -1.38,0 -2.5,-1.12 -2.5,-2.5 V 74 c 0,-1.38 1.12,-2.5 2.5,-2.5 h 30 c 1.38,0 2.5,1.12 2.5,2.5 v 30 c 0,1.38 -1.12,2.5 -2.5,2.5 z m -27.5,-5 h 25 v -25 h -25 z"
id="path931-5"
sodipodi:nodetypes="sssssssssccccc" /></g><g
id="g933-3-6"
transform="translate(66,16)"><path
fill="#ed6b21"
d="M 54,106.5 H 24 c -1.38,0 -2.5,-1.12 -2.5,-2.5 V 74 c 0,-1.38 1.12,-2.5 2.5,-2.5 h 30 c 1.38,0 2.5,1.12 2.5,2.5 v 30 c 0,1.38 -1.12,2.5 -2.5,2.5 z m -27.5,-5 h 25 v -25 h -25 z"
id="path931-5-2"
sodipodi:nodetypes="sssssssssccccc" /></g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.0"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 128 128"
enable-background="new 0 0 128 128"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs22" />
<g
id="arrange">
<g
id="g4">
<path
fill="#FFFFFF"
d="M120,122.5H8c-1.38,0-2.5-1.12-2.5-2.5V8c0-1.38,1.12-2.5,2.5-2.5h112c1.38,0,2.5,1.12,2.5,2.5v112 C122.5,121.38,121.38,122.5,120,122.5z M10.5,117.5h107v-107h-107V117.5z"
id="path2" />
</g>
<g
id="g8">
<path
fill="#ED6B21"
d="M104,58.5H24c-1.38,0-2.5-1.12-2.5-2.5V24c0-1.38,1.12-2.5,2.5-2.5h80c1.38,0,2.5,1.12,2.5,2.5v32 C106.5,57.38,105.38,58.5,104,58.5z M26.5,53.5h75v-27h-75V53.5z"
id="path6" />
</g>
<g
id="g12">
<path
fill="#ED6B21"
d="M48,106.5H24c-1.38,0-2.5-1.12-2.5-2.5V72c0-1.38,1.12-2.5,2.5-2.5h24c1.38,0,2.5,1.12,2.5,2.5v32 C50.5,105.38,49.38,106.5,48,106.5z M26.5,101.5h19v-27h-19V101.5z"
id="path10" />
</g>
<g
id="g16">
<path
fill="#ED6B21"
d="M104,106.5H64c-1.38,0-2.5-1.12-2.5-2.5V72c0-1.38,1.12-2.5,2.5-2.5h40c1.38,0,2.5,1.12,2.5,2.5v32 C106.5,105.38,105.38,106.5,104,106.5z M66.5,101.5h35v-27h-35V101.5z"
id="path14" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -388,7 +388,9 @@ int CLI::run(int argc, char **argv)
// Loop through transform options. // Loop through transform options.
bool user_center_specified = false; bool user_center_specified = false;
arr2::ArrangeBed bed = arr2::to_arrange_bed(get_bed_shape(m_print_config));
const BedsGrid::Gap gap{s_multiple_beds.get_bed_gap()};
arr2::ArrangeBed bed = arr2::to_arrange_bed(get_bed_shape(m_print_config), gap);
arr2::ArrangeSettings arrange_cfg; arr2::ArrangeSettings arrange_cfg;
arrange_cfg.set_distance_from_objects(min_object_distance(m_print_config)); arrange_cfg.set_distance_from_objects(min_object_distance(m_print_config));

View File

@ -46,7 +46,7 @@ void arrange(SelectionStrategy &&selstrategy,
// Dispatch: // Dispatch:
arrange(std::forward<SelectionStrategy>(selstrategy), arrange(std::forward<SelectionStrategy>(selstrategy),
std::forward<PackStrategy>(packingstrategy), items, fixed, std::forward<PackStrategy>(packingstrategy), items, fixed,
RectangleBed{bed.bb}, SelStrategyTag<SelectionStrategy>{}); RectangleBed{bed.bb, bed.gap}, SelStrategyTag<SelectionStrategy>{});
std::vector<int> bed_indices = get_bed_indices(items, fixed); std::vector<int> bed_indices = get_bed_indices(items, fixed);
std::map<int, BoundingBox> pilebb; std::map<int, BoundingBox> pilebb;
@ -399,6 +399,7 @@ ArrItem ConvexItemConverter<ArrItem>::convert(const Arrangeable &arrbl,
set_bed_index(ret, bed_index); set_bed_index(ret, bed_index);
set_priority(ret, arrbl.priority()); set_priority(ret, arrbl.priority());
set_bed_constraint(ret, arrbl.bed_constraint());
imbue_id(ret, arrbl.id()); imbue_id(ret, arrbl.id());
if constexpr (IsWritableDataStore<ArrItem>) if constexpr (IsWritableDataStore<ArrItem>)
@ -416,6 +417,7 @@ ArrItem AdvancedItemConverter<ArrItem>::convert(const Arrangeable &arrbl,
set_bed_index(ret, bed_index); set_bed_index(ret, bed_index);
set_priority(ret, arrbl.priority()); set_priority(ret, arrbl.priority());
set_bed_constraint(ret, arrbl.bed_constraint());
imbue_id(ret, arrbl.id()); imbue_id(ret, arrbl.id());
if constexpr (IsWritableDataStore<ArrItem>) if constexpr (IsWritableDataStore<ArrItem>)
arrbl.imbue_data(AnyWritableDataStore{ret}); arrbl.imbue_data(AnyWritableDataStore{ret});

View File

@ -139,6 +139,10 @@ void arrange(
int bedidx = 0; int bedidx = 0;
while (!was_packed && !is_cancelled()) { while (!was_packed && !is_cancelled()) {
for (; !was_packed && !is_cancelled(); bedidx++) { for (; !was_packed && !is_cancelled(); bedidx++) {
const std::optional<int> bed_constraint{get_bed_constraint(*it)};
if (bed_constraint && bedidx != *bed_constraint) {
continue;
}
set_bed_index(*it, bedidx); set_bed_index(*it, bedidx);
auto remaining = Range{std::next(static_cast<SConstIt>(it)), auto remaining = Range{std::next(static_cast<SConstIt>(it)),
@ -157,6 +161,10 @@ void arrange(
sel.on_arranged_fn(*it, bed, packed_range, remaining); sel.on_arranged_fn(*it, bed, packed_range, remaining);
} else { } else {
set_bed_index(*it, Unarranged); set_bed_index(*it, Unarranged);
if (bed_constraint && bedidx == *bed_constraint) {
// Leave the item as is as it does not fit on the enforced bed.
was_packed = true;
}
} }
} }
} }

View File

@ -31,6 +31,11 @@ template<class ArrItem, class En = void> struct ArrangeItemTraits_ {
static int get_bed_index(const ArrItem &ap) { return ap.get_bed_index(); } static int get_bed_index(const ArrItem &ap) { return ap.get_bed_index(); }
static std::optional<int> get_bed_constraint(const ArrItem &ap)
{
return ap.get_bed_constraint();
}
static int get_priority(const ArrItem &ap) { return ap.get_priority(); } static int get_priority(const ArrItem &ap) { return ap.get_priority(); }
// Setters: // Setters:
@ -43,6 +48,11 @@ template<class ArrItem, class En = void> struct ArrangeItemTraits_ {
static void set_rotation(ArrItem &ap, double v) { ap.set_rotation(v); } static void set_rotation(ArrItem &ap, double v) { ap.set_rotation(v); }
static void set_bed_index(ArrItem &ap, int v) { ap.set_bed_index(v); } static void set_bed_index(ArrItem &ap, int v) { ap.set_bed_index(v); }
static void set_bed_constraint(ArrItem &ap, std::optional<int> v)
{
ap.set_bed_constraint(v);
}
}; };
template<class T> using ArrangeItemTraits = ArrangeItemTraits_<StripCVRef<T>>; template<class T> using ArrangeItemTraits = ArrangeItemTraits_<StripCVRef<T>>;
@ -69,6 +79,11 @@ template<class T> int get_priority(const T &itm)
return ArrangeItemTraits<T>::get_priority(itm); return ArrangeItemTraits<T>::get_priority(itm);
} }
template<class T> std::optional<int> get_bed_constraint(const T &itm)
{
return ArrangeItemTraits<T>::get_bed_constraint(itm);
}
// Setters: // Setters:
template<class T> void set_translation(T &itm, const Vec2crd &v) template<class T> void set_translation(T &itm, const Vec2crd &v)
@ -86,6 +101,11 @@ template<class T> void set_bed_index(T &itm, int v)
ArrangeItemTraits<T>::set_bed_index(itm, v); ArrangeItemTraits<T>::set_bed_index(itm, v);
} }
template<class T> void set_bed_constraint(T &itm, std::optional<int> v)
{
ArrangeItemTraits<T>::set_bed_constraint(itm, v);
}
// Helper functions for arrange items // Helper functions for arrange items
template<class ArrItem> bool is_arranged(const ArrItem &ap) template<class ArrItem> bool is_arranged(const ArrItem &ap)
{ {

View File

@ -83,7 +83,7 @@ inline double distance_to(const Point &p1, const Point &p2)
return std::sqrt(dx * dx + dy * dy); return std::sqrt(dx * dx + dy * dy);
} }
static CircleBed to_circle(const Point &center, const Points &points) static CircleBed to_circle(const Point &center, const Points &points, const BedsGrid::Gap &gap)
{ {
std::vector<double> vertex_distances; std::vector<double> vertex_distances;
double avg_dist = 0; double avg_dist = 0;
@ -96,7 +96,7 @@ static CircleBed to_circle(const Point &center, const Points &points)
avg_dist /= vertex_distances.size(); avg_dist /= vertex_distances.size();
CircleBed ret(center, avg_dist); CircleBed ret(center, avg_dist, gap);
for (auto el : vertex_distances) { for (auto el : vertex_distances) {
if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) { if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) {
ret = {}; ret = {};
@ -107,7 +107,7 @@ static CircleBed to_circle(const Point &center, const Points &points)
return ret; return ret;
} }
template<class Fn> auto call_with_bed(const Points &bed, Fn &&fn) template<class Fn> auto call_with_bed(const Points &bed, const BedsGrid::Gap &gap, Fn &&fn)
{ {
if (bed.empty()) if (bed.empty())
return fn(InfiniteBed{}); return fn(InfiniteBed{});
@ -115,23 +115,23 @@ template<class Fn> auto call_with_bed(const Points &bed, Fn &&fn)
return fn(InfiniteBed{bed.front()}); return fn(InfiniteBed{bed.front()});
else { else {
auto bb = BoundingBox(bed); auto bb = BoundingBox(bed);
CircleBed circ = to_circle(bb.center(), bed); CircleBed circ = to_circle(bb.center(), bed, gap);
auto parea = poly_area(bed); auto parea = poly_area(bed);
if ((1.0 - parea / area(bb)) < 1e-3) { if ((1.0 - parea / area(bb)) < 1e-3) {
return fn(RectangleBed{bb}); return fn(RectangleBed{bb, gap});
} else if (!std::isnan(circ.radius()) && (1.0 - parea / area(circ)) < 1e-2) } else if (!std::isnan(circ.radius()) && (1.0 - parea / area(circ)) < 1e-2)
return fn(circ); return fn(circ);
else else
return fn(IrregularBed{{ExPolygon(bed)}}); return fn(IrregularBed{{ExPolygon(bed)}, gap});
} }
} }
ArrangeBed to_arrange_bed(const Points &bedpts) ArrangeBed to_arrange_bed(const Points &bedpts, const BedsGrid::Gap &gap)
{ {
ArrangeBed ret; ArrangeBed ret;
call_with_bed(bedpts, [&](const auto &bed) { ret = bed; }); call_with_bed(bedpts, gap, [&](const auto &bed) { ret = bed; });
return ret; return ret;
} }

View File

@ -16,6 +16,7 @@
#include <limits> #include <limits>
#include <type_traits> #include <type_traits>
#include "libslic3r/MultipleBeds.hpp"
#include "libslic3r/Polygon.hpp" #include "libslic3r/Polygon.hpp"
#include "libslic3r/libslic3r.h" #include "libslic3r/libslic3r.h"
@ -35,13 +36,18 @@ struct InfiniteBed {
BoundingBox bounding_box(const InfiniteBed &bed); BoundingBox bounding_box(const InfiniteBed &bed);
inline InfiniteBed offset(const InfiniteBed &bed, coord_t) { return bed; } inline InfiniteBed offset(const InfiniteBed &bed, coord_t) { return bed; }
inline BedsGrid::Gap bed_gap(const InfiniteBed &)
{
return BedsGrid::Gap::Zero();
}
struct RectangleBed { struct RectangleBed {
BoundingBox bb; BoundingBox bb;
BedsGrid::Gap gap;
explicit RectangleBed(const BoundingBox &bedbb) : bb{bedbb} {} explicit RectangleBed(const BoundingBox &bedbb, const BedsGrid::Gap &gap) : bb{bedbb}, gap{gap} {}
explicit RectangleBed(coord_t w, coord_t h, Point c = {0, 0}): explicit RectangleBed(coord_t w, coord_t h, const BedsGrid::Gap &gap = BedsGrid::Gap::Zero(), Point c = {0, 0}):
bb{{c.x() - w / 2, c.y() - h / 2}, {c.x() + w / 2, c.y() + h / 2}} bb{{c.x() - w / 2, c.y() - h / 2}, {c.x() + w / 2, c.y() + h / 2}}, gap{gap}
{} {}
coord_t width() const { return bb.size().x(); } coord_t width() const { return bb.size().x(); }
@ -54,6 +60,9 @@ inline RectangleBed offset(RectangleBed bed, coord_t v)
bed.bb.offset(v); bed.bb.offset(v);
return bed; return bed;
} }
inline BedsGrid::Gap bed_gap(const RectangleBed &bed) {
return bed.gap;
}
Polygon to_rectangle(const BoundingBox &bb); Polygon to_rectangle(const BoundingBox &bb);
@ -65,16 +74,19 @@ inline Polygon to_rectangle(const RectangleBed &bed)
class CircleBed { class CircleBed {
Point m_center; Point m_center;
double m_radius; double m_radius;
BedsGrid::Gap m_gap;
public: public:
CircleBed(): m_center(0, 0), m_radius(NaNd) {} CircleBed(): m_center(0, 0), m_radius(NaNd), m_gap(BedsGrid::Gap::Zero()) {}
explicit CircleBed(const Point& c, double r) explicit CircleBed(const Point& c, double r, const BedsGrid::Gap &g)
: m_center(c) : m_center(c)
, m_radius(r) , m_radius(r)
, m_gap(g)
{} {}
double radius() const { return m_radius; } double radius() const { return m_radius; }
const Point& center() const { return m_center; } const Point& center() const { return m_center; }
const BedsGrid::Gap &gap() const { return m_gap; }
}; };
// Function to approximate a circle with a convex polygon // Function to approximate a circle with a convex polygon
@ -89,10 +101,14 @@ inline BoundingBox bounding_box(const CircleBed &bed)
} }
inline CircleBed offset(const CircleBed &bed, coord_t v) inline CircleBed offset(const CircleBed &bed, coord_t v)
{ {
return CircleBed{bed.center(), bed.radius() + v}; return CircleBed{bed.center(), bed.radius() + v, bed.gap()};
}
inline BedsGrid::Gap bed_gap(const CircleBed &bed)
{
return bed.gap();
} }
struct IrregularBed { ExPolygons poly; }; struct IrregularBed { ExPolygons poly; BedsGrid::Gap gap; };
inline BoundingBox bounding_box(const IrregularBed &bed) inline BoundingBox bounding_box(const IrregularBed &bed)
{ {
return get_extents(bed.poly); return get_extents(bed.poly);
@ -103,6 +119,10 @@ inline IrregularBed offset(IrregularBed bed, coord_t v)
bed.poly = offset_ex(bed.poly, v); bed.poly = offset_ex(bed.poly, v);
return bed; return bed;
} }
inline BedsGrid::Gap bed_gap(const IrregularBed &bed)
{
return bed.gap;
}
using ArrangeBed = using ArrangeBed =
boost::variant<InfiniteBed, RectangleBed, CircleBed, IrregularBed>; boost::variant<InfiniteBed, RectangleBed, CircleBed, IrregularBed>;
@ -124,6 +144,15 @@ inline ArrangeBed offset(ArrangeBed bed, coord_t v)
return bed; return bed;
} }
inline BedsGrid::Gap bed_gap(const ArrangeBed &bed)
{
BedsGrid::Gap ret;
auto visitor = [&ret](const auto &b) { ret = bed_gap(b); };
boost::apply_visitor(visitor, bed);
return ret;
}
inline double area(const BoundingBox &bb) inline double area(const BoundingBox &bb)
{ {
auto bbsz = bb.size(); auto bbsz = bb.size();
@ -187,7 +216,7 @@ inline ExPolygons to_expolygons(const ArrangeBed &bed)
return ret; return ret;
} }
ArrangeBed to_arrange_bed(const Points &bedpts); ArrangeBed to_arrange_bed(const Points &bedpts, const BedsGrid::Gap &gap);
template<class Bed, class En = void> struct IsRectangular_ : public std::false_type {}; template<class Bed, class En = void> struct IsRectangular_ : public std::false_type {};
template<> struct IsRectangular_<RectangleBed>: public std::true_type {}; template<> struct IsRectangular_<RectangleBed>: public std::true_type {};

View File

@ -74,7 +74,7 @@ struct RectangleOverfitKernelWrapper {
for (auto &fitm : all_items_range(packing_context)) for (auto &fitm : all_items_range(packing_context))
pilebb.merge(fixed_bounding_box(fitm)); pilebb.merge(fixed_bounding_box(fitm));
return KernelTraits<Kernel>::on_start_packing(k, itm, RectangleBed{binbb}, return KernelTraits<Kernel>::on_start_packing(k, itm, RectangleBed{binbb, BedsGrid::Gap::Zero()},
packing_context, packing_context,
remaining_items); remaining_items);
} }

View File

@ -9,9 +9,9 @@
#include "KernelTraits.hpp" #include "KernelTraits.hpp"
#include "libslic3r/Arrange/Core/PackingContext.hpp" #include "arrange/PackingContext.hpp"
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" #include "arrange/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/Beds.hpp" #include "arrange/Beds.hpp"
#include <libslic3r/SVG.hpp> #include <libslic3r/SVG.hpp>

View File

@ -159,6 +159,7 @@ ArrangeItem &ArrangeItem::operator=(const ArrangeItem &other)
m_datastore = other.m_datastore; m_datastore = other.m_datastore;
m_bed_idx = other.m_bed_idx; m_bed_idx = other.m_bed_idx;
m_priority = other.m_priority; m_priority = other.m_priority;
m_bed_constraint = other.m_bed_constraint;
if (other.m_envelope.get() == &other.m_shape) if (other.m_envelope.get() == &other.m_shape)
m_envelope = &m_shape; m_envelope = &m_shape;
@ -190,6 +191,7 @@ ArrangeItem &ArrangeItem::operator=(ArrangeItem &&other) noexcept
m_datastore = std::move(other.m_datastore); m_datastore = std::move(other.m_datastore);
m_bed_idx = other.m_bed_idx; m_bed_idx = other.m_bed_idx;
m_priority = other.m_priority; m_priority = other.m_priority;
m_bed_constraint = other.m_bed_constraint;
if (other.m_envelope.get() == &other.m_shape) if (other.m_envelope.get() == &other.m_shape)
m_envelope = &m_shape; m_envelope = &m_shape;

View File

@ -154,6 +154,7 @@ private:
int m_bed_idx{Unarranged}; // To which logical bed does this item belong int m_bed_idx{Unarranged}; // To which logical bed does this item belong
int m_priority{0}; // For sorting int m_priority{0}; // For sorting
std::optional<int> m_bed_constraint;
public: public:
ArrangeItem() = default; ArrangeItem() = default;
@ -180,9 +181,11 @@ public:
int bed_idx() const { return m_bed_idx; } int bed_idx() const { return m_bed_idx; }
int priority() const { return m_priority; } int priority() const { return m_priority; }
std::optional<int> bed_constraint() const { return m_bed_constraint; };
void bed_idx(int v) { m_bed_idx = v; } void bed_idx(int v) { m_bed_idx = v; }
void priority(int v) { m_priority = v; } void priority(int v) { m_priority = v; }
void bed_constraint(std::optional<int> v) { m_bed_constraint = v; }
const ArbitraryDataStore &datastore() const { return m_datastore; } const ArbitraryDataStore &datastore() const { return m_datastore; }
ArbitraryDataStore &datastore() { return m_datastore; } ArbitraryDataStore &datastore() { return m_datastore; }
@ -239,6 +242,11 @@ template<> struct ArrangeItemTraits_<ArrangeItem>
return itm.priority(); return itm.priority();
} }
static std::optional<int> get_bed_constraint(const ArrangeItem &itm)
{
return itm.bed_constraint();
}
// Setters: // Setters:
static void set_translation(ArrangeItem &itm, const Vec2crd &v) static void set_translation(ArrangeItem &itm, const Vec2crd &v)
@ -255,6 +263,11 @@ template<> struct ArrangeItemTraits_<ArrangeItem>
{ {
itm.bed_idx(v); itm.bed_idx(v);
} }
static void set_bed_constraint(ArrangeItem &itm, std::optional<int> v)
{
itm.bed_constraint(v);
}
}; };
// Some items can be containers of arbitrary data stored under string keys. // Some items can be containers of arbitrary data stored under string keys.

View File

@ -37,6 +37,7 @@ class SimpleArrangeItem {
double m_rotation = 0.; double m_rotation = 0.;
int m_priority = 0; int m_priority = 0;
int m_bed_idx = Unarranged; int m_bed_idx = Unarranged;
std::optional<int> m_bed_constraint;
std::vector<double> m_allowed_rotations = {0.}; std::vector<double> m_allowed_rotations = {0.};
ObjectID m_obj_id; ObjectID m_obj_id;
@ -50,11 +51,15 @@ public:
double get_rotation() const noexcept { return m_rotation; } double get_rotation() const noexcept { return m_rotation; }
int get_priority() const noexcept { return m_priority; } int get_priority() const noexcept { return m_priority; }
int get_bed_index() const noexcept { return m_bed_idx; } int get_bed_index() const noexcept { return m_bed_idx; }
std::optional<int> get_bed_constraint() const noexcept {
return m_bed_constraint;
}
void set_translation(const Vec2crd &v) { m_translation = v; } void set_translation(const Vec2crd &v) { m_translation = v; }
void set_rotation(double v) noexcept { m_rotation = v; } void set_rotation(double v) noexcept { m_rotation = v; }
void set_priority(int v) noexcept { m_priority = v; } void set_priority(int v) noexcept { m_priority = v; }
void set_bed_index(int v) noexcept { m_bed_idx = v; } void set_bed_index(int v) noexcept { m_bed_idx = v; }
void set_bed_constraint(std::optional<int> v) noexcept { m_bed_constraint = v; }
const Polygon &shape() const { return m_shape; } const Polygon &shape() const { return m_shape; }
Polygon outline() const; Polygon outline() const;

View File

@ -17,6 +17,7 @@ class TrafoOnlyArrangeItem {
int m_priority = 0; int m_priority = 0;
Vec2crd m_translation = Vec2crd::Zero(); Vec2crd m_translation = Vec2crd::Zero();
double m_rotation = 0.; double m_rotation = 0.;
std::optional<int> m_bed_constraint;
ArbitraryDataStore m_datastore; ArbitraryDataStore m_datastore;
@ -28,13 +29,15 @@ public:
: m_bed_idx{arr2::get_bed_index(other)}, : m_bed_idx{arr2::get_bed_index(other)},
m_priority{arr2::get_priority(other)}, m_priority{arr2::get_priority(other)},
m_translation(arr2::get_translation(other)), m_translation(arr2::get_translation(other)),
m_rotation{arr2::get_rotation(other)} m_rotation{arr2::get_rotation(other)},
m_bed_constraint{arr2::get_bed_constraint(other)}
{} {}
const Vec2crd& get_translation() const noexcept { return m_translation; } const Vec2crd& get_translation() const noexcept { return m_translation; }
double get_rotation() const noexcept { return m_rotation; } double get_rotation() const noexcept { return m_rotation; }
int get_bed_index() const noexcept { return m_bed_idx; } int get_bed_index() const noexcept { return m_bed_idx; }
int get_priority() const noexcept { return m_priority; } int get_priority() const noexcept { return m_priority; }
std::optional<int> get_bed_constraint() const noexcept { return m_bed_constraint; }
const ArbitraryDataStore &datastore() const noexcept { return m_datastore; } const ArbitraryDataStore &datastore() const noexcept { return m_datastore; }
ArbitraryDataStore &datastore() { return m_datastore; } ArbitraryDataStore &datastore() { return m_datastore; }

View File

@ -99,6 +99,8 @@ public:
// objects are arranged first. // objects are arranged first.
virtual int priority() const { return 0; } virtual int priority() const { return 0; }
virtual std::optional<int> bed_constraint() const { return std::nullopt; }
// Any implementation specific properties can be passed to the arrangement // Any implementation specific properties can be passed to the arrangement
// core by overriding this method. This implies that the specific Arranger // core by overriding this method. This implies that the specific Arranger
// will be able to interpret these properties. An example usage is to mark // will be able to interpret these properties. An example usage is to mark
@ -190,6 +192,14 @@ inline BoundingBox bounding_box(const ExtendedBed &bed)
return bedbb; return bedbb;
} }
inline BedsGrid::Gap bed_gap(const ExtendedBed &bed)
{
BedsGrid::Gap gap;
visit_bed([&gap](auto &rawbed) { gap = bed_gap(rawbed); }, bed);
return gap;
}
class Scene; class Scene;
// SceneBuilderBase is intended for Scene construction. A simple constructor // SceneBuilderBase is intended for Scene construction. A simple constructor
@ -238,9 +248,9 @@ public:
return std::move(static_cast<Subclass&>(*this)); return std::move(static_cast<Subclass&>(*this));
} }
Subclass &&set_bed(const Points &pts) Subclass &&set_bed(const Points &pts, const BedsGrid::Gap &gap)
{ {
m_bed = arr2::to_arrange_bed(pts); m_bed = arr2::to_arrange_bed(pts, gap);
return std::move(static_cast<Subclass&>(*this)); return std::move(static_cast<Subclass&>(*this));
} }

View File

@ -14,6 +14,7 @@
#include <iterator> #include <iterator>
#include "libslic3r/Model.hpp" #include "libslic3r/Model.hpp"
#include "libslic3r/MultipleBeds.hpp"
#include "libslic3r/Print.hpp" #include "libslic3r/Print.hpp"
#include "libslic3r/SLAPrint.hpp" #include "libslic3r/SLAPrint.hpp"
#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp" #include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp"
@ -189,7 +190,7 @@ void SceneBuilder::build_scene(Scene &sc) &&
if (m_fff_print && !m_sla_print) { if (m_fff_print && !m_sla_print) {
if (is_infinite_bed(m_bed)) { if (is_infinite_bed(m_bed)) {
set_bed(*m_fff_print); set_bed(*m_fff_print, BedsGrid::Gap::Zero());
} else { } else {
set_brim_and_skirt(); set_brim_and_skirt();
} }
@ -211,28 +212,28 @@ void SceneBuilder::build_arrangeable_slicer_model(ArrangeableSlicerModel &amodel
m_vbed_handler = VirtualBedHandler::create(m_bed); m_vbed_handler = VirtualBedHandler::create(m_bed);
} }
if (!m_wipetower_handler) {
m_wipetower_handler = std::make_unique<MissingWipeTowerHandler>();
}
if (m_fff_print && !m_xl_printer) if (m_fff_print && !m_xl_printer)
m_xl_printer = is_XL_printer(m_fff_print->config()); m_xl_printer = is_XL_printer(m_fff_print->config());
bool has_wipe_tower = false; const bool has_wipe_tower = !m_wipetower_handlers.empty();
m_wipetower_handler->visit(
[&has_wipe_tower](const Arrangeable &arrbl) { has_wipe_tower = true; });
if (m_xl_printer && !has_wipe_tower) { if (m_xl_printer && !has_wipe_tower) {
m_bed = XLBed{bounding_box(m_bed)}; m_bed = XLBed{bounding_box(m_bed), bed_gap(m_bed)};
} }
amodel.m_vbed_handler = std::move(m_vbed_handler); amodel.m_vbed_handler = std::move(m_vbed_handler);
amodel.m_model = std::move(m_model); amodel.m_model = std::move(m_model);
amodel.m_selmask = std::move(m_selection); amodel.m_selmask = std::move(m_selection);
amodel.m_wth = std::move(m_wipetower_handler); amodel.m_wths = std::move(m_wipetower_handlers);
amodel.m_bed_constraints = std::move(m_bed_constraints);
amodel.m_wth->set_selection_predicate( for (auto &wth : amodel.m_wths) {
[&amodel] { return amodel.m_selmask->is_wipe_tower(); }); wth->set_selection_predicate(
[&amodel](int wipe_tower_index){
return amodel.m_selmask->is_wipe_tower_selected(wipe_tower_index);
}
);
}
} }
int XStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const int XStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const
@ -331,40 +332,19 @@ Transform3d YStriderVBedHandler::get_physical_bed_trafo(int bed_index) const
return tr; return tr;
} }
const int GridStriderVBedHandler::Cols =
2 * static_cast<int>(std::sqrt(std::numeric_limits<int>::max()) / 2);
const int GridStriderVBedHandler::HalfCols = Cols / 2;
const int GridStriderVBedHandler::Offset = HalfCols + Cols * HalfCols;
Vec2i GridStriderVBedHandler::raw2grid(int bed_idx) const
{
bed_idx += Offset;
Vec2i ret{bed_idx % Cols - HalfCols, bed_idx / Cols - HalfCols};
return ret;
}
int GridStriderVBedHandler::grid2raw(const Vec2i &crd) const
{
// Overlapping virtual beds will happen if the crd values exceed limits
assert((crd.x() < HalfCols - 1 && crd.x() >= -HalfCols) &&
(crd.y() < HalfCols - 1 && crd.y() >= -HalfCols));
return (crd.x() + HalfCols) + Cols * (crd.y() + HalfCols) - Offset;
}
int GridStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const int GridStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const
{ {
Vec2i crd = {m_xstrider.get_bed_index(obj), m_ystrider.get_bed_index(obj)}; Vec2i crd = {m_xstrider.get_bed_index(obj), m_ystrider.get_bed_index(obj)};
return grid2raw(crd); return BedsGrid::grid_coords2index(crd);
} }
bool GridStriderVBedHandler::assign_bed(VBedPlaceable &inst, int bed_idx) bool GridStriderVBedHandler::assign_bed(VBedPlaceable &inst, int bed_idx)
{ {
Vec2i crd = raw2grid(bed_idx); if (bed_idx < 0) {
return false;
}
Vec2i crd = BedsGrid::index2grid_coords(bed_idx);
bool retx = m_xstrider.assign_bed(inst, crd.x()); bool retx = m_xstrider.assign_bed(inst, crd.x());
bool rety = m_ystrider.assign_bed(inst, crd.y()); bool rety = m_ystrider.assign_bed(inst, crd.y());
@ -374,7 +354,7 @@ bool GridStriderVBedHandler::assign_bed(VBedPlaceable &inst, int bed_idx)
Transform3d GridStriderVBedHandler::get_physical_bed_trafo(int bed_idx) const Transform3d GridStriderVBedHandler::get_physical_bed_trafo(int bed_idx) const
{ {
Vec2i crd = raw2grid(bed_idx); Vec2i crd = BedsGrid::index2grid_coords(bed_idx);
Transform3d ret = m_xstrider.get_physical_bed_trafo(crd.x()) * Transform3d ret = m_xstrider.get_physical_bed_trafo(crd.x()) *
m_ystrider.get_physical_bed_trafo(crd.y()); m_ystrider.get_physical_bed_trafo(crd.y());
@ -465,7 +445,7 @@ SceneBuilder &&SceneBuilder::set_sla_print(AnyPtr<const SLAPrint> mdl_print)
return std::move(*this); return std::move(*this);
} }
SceneBuilder &&SceneBuilder::set_bed(const DynamicPrintConfig &cfg) SceneBuilder &&SceneBuilder::set_bed(const DynamicPrintConfig &cfg, const BedsGrid::Gap &gap)
{ {
Points bedpts = get_bed_shape(cfg); Points bedpts = get_bed_shape(cfg);
@ -473,19 +453,19 @@ SceneBuilder &&SceneBuilder::set_bed(const DynamicPrintConfig &cfg)
m_xl_printer = true; m_xl_printer = true;
} }
m_bed = arr2::to_arrange_bed(bedpts); m_bed = arr2::to_arrange_bed(bedpts, gap);
return std::move(*this); return std::move(*this);
} }
SceneBuilder &&SceneBuilder::set_bed(const Print &print) SceneBuilder &&SceneBuilder::set_bed(const Print &print, const BedsGrid::Gap &gap)
{ {
Points bedpts = get_bed_shape(print.config()); Points bedpts = get_bed_shape(print.config());
if (is_XL_printer(print.config())) { if (is_XL_printer(print.config())) {
m_bed = XLBed{get_extents(bedpts)}; m_bed = XLBed{get_extents(bedpts), gap};
} else { } else {
m_bed = arr2::to_arrange_bed(bedpts); m_bed = arr2::to_arrange_bed(bedpts, gap);
} }
set_brim_and_skirt(); set_brim_and_skirt();
@ -499,11 +479,13 @@ SceneBuilder &&SceneBuilder::set_sla_print(const SLAPrint *slaprint)
return std::move(*this); return std::move(*this);
} }
int ArrangeableWipeTowerBase::get_bed_index() const { return PhysicalBedId; } int ArrangeableWipeTowerBase::get_bed_index() const {
return this->bed_index;
}
bool ArrangeableWipeTowerBase::assign_bed(int bed_idx) bool ArrangeableWipeTowerBase::assign_bed(int bed_idx)
{ {
return bed_idx == PhysicalBedId; return bed_idx == this->bed_index;
} }
bool PhysicalOnlyVBedHandler::assign_bed(VBedPlaceable &inst, int bed_idx) bool PhysicalOnlyVBedHandler::assign_bed(VBedPlaceable &inst, int bed_idx)
@ -523,7 +505,9 @@ void ArrangeableSlicerModel::for_each_arrangeable(
{ {
for_each_arrangeable_(*this, fn); for_each_arrangeable_(*this, fn);
m_wth->visit(fn); for (auto &wth : m_wths) {
wth->visit(fn);
}
} }
void ArrangeableSlicerModel::for_each_arrangeable( void ArrangeableSlicerModel::for_each_arrangeable(
@ -531,7 +515,9 @@ void ArrangeableSlicerModel::for_each_arrangeable(
{ {
for_each_arrangeable_(*this, fn); for_each_arrangeable_(*this, fn);
m_wth->visit(fn); for (auto &wth : m_wths) {
wth->visit(fn);
}
} }
ObjectID ArrangeableSlicerModel::add_arrangeable(const ObjectID &prototype_id) ObjectID ArrangeableSlicerModel::add_arrangeable(const ObjectID &prototype_id)
@ -549,13 +535,30 @@ ObjectID ArrangeableSlicerModel::add_arrangeable(const ObjectID &prototype_id)
return ret; return ret;
} }
std::optional<int> get_bed_constraint(
const ObjectID &id,
const BedConstraints &bed_constraints
) {
const auto found_constraint{bed_constraints.find(id)};
if (found_constraint == bed_constraints.end()) {
return std::nullopt;
}
return found_constraint->second;
}
template<class Self, class Fn> template<class Self, class Fn>
void ArrangeableSlicerModel::for_each_arrangeable_(Self &&self, Fn &&fn) void ArrangeableSlicerModel::for_each_arrangeable_(Self &&self, Fn &&fn)
{ {
InstPos pos; InstPos pos;
for (auto *obj : self.m_model->objects) { for (auto *obj : self.m_model->objects) {
for (auto *inst : obj->instances) { for (auto *inst : obj->instances) {
ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), self.m_selmask.get(), pos}; ArrangeableModelInstance ainst{
inst,
self.m_vbed_handler.get(),
self.m_selmask.get(),
pos,
get_bed_constraint(inst->id(), self.m_bed_constraints)
};
fn(ainst); fn(ainst);
++pos.inst_idx; ++pos.inst_idx;
} }
@ -567,16 +570,23 @@ void ArrangeableSlicerModel::for_each_arrangeable_(Self &&self, Fn &&fn)
template<class Self, class Fn> template<class Self, class Fn>
void ArrangeableSlicerModel::visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn) void ArrangeableSlicerModel::visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn)
{ {
if (id == wipe_tower_instance_id(0)) { for (auto &wth : self.m_wths) {
self.m_wth->visit(fn); if (id == wth->get_id()) {
wth->visit(fn);
return; return;
}
} }
auto [inst, pos] = find_instance_by_id(*self.m_model, id); auto [inst, pos] = find_instance_by_id(*self.m_model, id);
if (inst) { if (inst) {
ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), self.m_selmask.get(), pos}; ArrangeableModelInstance ainst{
inst,
self.m_vbed_handler.get(),
self.m_selmask.get(),
pos,
get_bed_constraint(id, self.m_bed_constraints)
};
fn(ainst); fn(ainst);
} }
} }
@ -600,7 +610,7 @@ void ArrangeableSLAPrint::for_each_arrangeable_(Self &&self, Fn &&fn)
for (auto *obj : self.m_model->objects) { for (auto *obj : self.m_model->objects) {
for (auto *inst : obj->instances) { for (auto *inst : obj->instances) {
ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(),
self.m_selmask.get(), pos}; self.m_selmask.get(), pos, std::nullopt};
auto obj_id = inst->get_object()->id(); auto obj_id = inst->get_object()->id();
const SLAPrintObject *po = const SLAPrintObject *po =
@ -627,7 +637,9 @@ void ArrangeableSLAPrint::for_each_arrangeable(
{ {
for_each_arrangeable_(*this, fn); for_each_arrangeable_(*this, fn);
m_wth->visit(fn); for (auto &wth : m_wths) {
wth->visit(fn);
}
} }
void ArrangeableSLAPrint::for_each_arrangeable( void ArrangeableSLAPrint::for_each_arrangeable(
@ -635,7 +647,9 @@ void ArrangeableSLAPrint::for_each_arrangeable(
{ {
for_each_arrangeable_(*this, fn); for_each_arrangeable_(*this, fn);
m_wth->visit(fn); for (auto &wth : m_wths) {
wth->visit(fn);
}
} }
template<class Self, class Fn> template<class Self, class Fn>
@ -645,7 +659,7 @@ void ArrangeableSLAPrint::visit_arrangeable_(Self &&self, const ObjectID &id, Fn
if (inst) { if (inst) {
ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(),
self.m_selmask.get(), pos}; self.m_selmask.get(), pos, std::nullopt};
auto obj_id = inst->get_object()->id(); auto obj_id = inst->get_object()->id();
const SLAPrintObject *po = const SLAPrintObject *po =
@ -925,16 +939,12 @@ std::unique_ptr<VirtualBedHandler> VirtualBedHandler::create(const ExtendedBed &
if (is_infinite_bed(bed)) { if (is_infinite_bed(bed)) {
ret = std::make_unique<PhysicalOnlyVBedHandler>(); ret = std::make_unique<PhysicalOnlyVBedHandler>();
} else { } else {
// The gap between logical beds expressed in ratio of BedsGrid::Gap gap;
// the current bed width. visit_bed([&gap](auto &rawbed) { gap = bed_gap(rawbed); }, bed);
constexpr double LogicalBedGap = 1. / 10.;
BoundingBox bedbb; BoundingBox bedbb;
visit_bed([&bedbb](auto &rawbed) { bedbb = bounding_box(rawbed); }, bed); visit_bed([&bedbb](auto &rawbed) { bedbb = bounding_box(rawbed); }, bed);
auto bedwidth = bedbb.size().x(); ret = std::make_unique<GridStriderVBedHandler>(bedbb, gap);
coord_t xgap = LogicalBedGap * bedwidth;
ret = std::make_unique<GridStriderVBedHandler>(bedbb, xgap);
} }
return ret; return ret;

View File

@ -42,7 +42,7 @@ class DynamicPrintConfig;
namespace arr2 { namespace arr2 {
using SelectionPredicate = std::function<bool()>; using SelectionPredicate = std::function<bool(int)>;
// Objects implementing this interface should know how to present the wipe tower // Objects implementing this interface should know how to present the wipe tower
// as an Arrangeable. If the wipe tower is not present, the overloads of visit() shouldn't do // as an Arrangeable. If the wipe tower is not present, the overloads of visit() shouldn't do
@ -55,6 +55,7 @@ public:
virtual void visit(std::function<void(Arrangeable &)>) = 0; virtual void visit(std::function<void(Arrangeable &)>) = 0;
virtual void visit(std::function<void(const Arrangeable &)>) const = 0; virtual void visit(std::function<void(const Arrangeable &)>) const = 0;
virtual void set_selection_predicate(SelectionPredicate pred) = 0; virtual void set_selection_predicate(SelectionPredicate pred) = 0;
virtual ObjectID get_id() const = 0;
}; };
// Something that has a bounding box and can be displaced by arbitrary 2D offset and rotated // Something that has a bounding box and can be displaced by arbitrary 2D offset and rotated
@ -110,7 +111,7 @@ public:
virtual std::vector<bool> selected_objects() const = 0; virtual std::vector<bool> selected_objects() const = 0;
virtual std::vector<bool> selected_instances(int obj_id) const = 0; virtual std::vector<bool> selected_instances(int obj_id) const = 0;
virtual bool is_wipe_tower() const = 0; virtual bool is_wipe_tower_selected(int wipe_tower_index) const = 0;
}; };
class FixedSelection : public Slic3r::arr2::SelectionMask class FixedSelection : public Slic3r::arr2::SelectionMask
@ -138,7 +139,7 @@ public:
std::vector<bool>{}; std::vector<bool>{};
} }
bool is_wipe_tower() const override { return m_wp; } bool is_wipe_tower_selected(int) const override { return m_wp; }
}; };
// Common part of any Arrangeable which is a wipe tower // Common part of any Arrangeable which is a wipe tower
@ -148,13 +149,16 @@ struct ArrangeableWipeTowerBase: public Arrangeable
Polygon poly; Polygon poly;
SelectionPredicate selection_pred; SelectionPredicate selection_pred;
int bed_index{0};
ArrangeableWipeTowerBase( ArrangeableWipeTowerBase(
const ObjectID &objid, const ObjectID &objid,
Polygon shape, Polygon shape,
SelectionPredicate selection_predicate = [] { return false; }) int bed_index,
SelectionPredicate selection_predicate = [](int){ return false; })
: oid{objid}, : oid{objid},
poly{std::move(shape)}, poly{std::move(shape)},
bed_index{bed_index},
selection_pred{std::move(selection_predicate)} selection_pred{std::move(selection_predicate)}
{} {}
@ -174,7 +178,7 @@ struct ArrangeableWipeTowerBase: public Arrangeable
bool is_selected() const override bool is_selected() const override
{ {
return selection_pred(); return selection_pred(bed_index);
} }
int get_bed_index() const override; int get_bed_index() const override;
@ -182,6 +186,10 @@ struct ArrangeableWipeTowerBase: public Arrangeable
int priority() const override { return 1; } int priority() const override { return 1; }
std::optional<int> bed_constraint() const override {
return this->bed_index;
}
void transform(const Vec2d &transl, double rot) override {} void transform(const Vec2d &transl, double rot) override {}
void imbue_data(AnyWritable &datastore) const override void imbue_data(AnyWritable &datastore) const override
@ -194,15 +202,18 @@ class SceneBuilder;
struct InstPos { size_t obj_idx = 0, inst_idx = 0; }; struct InstPos { size_t obj_idx = 0, inst_idx = 0; };
using BedConstraints = std::map<ObjectID, int>;
// Implementing ArrangeableModel interface for PrusaSlicer's Model, ModelObject, ModelInstance data // Implementing ArrangeableModel interface for PrusaSlicer's Model, ModelObject, ModelInstance data
// hierarchy // hierarchy
class ArrangeableSlicerModel: public ArrangeableModel class ArrangeableSlicerModel: public ArrangeableModel
{ {
protected: protected:
AnyPtr<Model> m_model; AnyPtr<Model> m_model;
AnyPtr<WipeTowerHandler> m_wth; // Determines how wipe tower is handled std::vector<AnyPtr<WipeTowerHandler>> m_wths; // Determines how wipe tower is handled
AnyPtr<VirtualBedHandler> m_vbed_handler; // Determines how virtual beds are handled AnyPtr<VirtualBedHandler> m_vbed_handler; // Determines how virtual beds are handled
AnyPtr<const SelectionMask> m_selmask; // Determines which objects are selected/unselected AnyPtr<const SelectionMask> m_selmask; // Determines which objects are selected/unselected
BedConstraints m_bed_constraints;
private: private:
friend class SceneBuilder; friend class SceneBuilder;
@ -234,7 +245,8 @@ class SceneBuilder: public SceneBuilderBase<SceneBuilder>
{ {
protected: protected:
AnyPtr<Model> m_model; AnyPtr<Model> m_model;
AnyPtr<WipeTowerHandler> m_wipetower_handler; std::vector<AnyPtr<WipeTowerHandler>> m_wipetower_handlers;
BedConstraints m_bed_constraints;
AnyPtr<VirtualBedHandler> m_vbed_handler; AnyPtr<VirtualBedHandler> m_vbed_handler;
AnyPtr<const SelectionMask> m_selection; AnyPtr<const SelectionMask> m_selection;
@ -259,18 +271,18 @@ public:
using SceneBuilderBase<SceneBuilder>::set_bed; using SceneBuilderBase<SceneBuilder>::set_bed;
SceneBuilder &&set_bed(const DynamicPrintConfig &cfg); SceneBuilder &&set_bed(const DynamicPrintConfig &cfg, const BedsGrid::Gap &gap);
SceneBuilder &&set_bed(const Print &print); SceneBuilder &&set_bed(const Print &print, const BedsGrid::Gap &gap);
SceneBuilder && set_wipe_tower_handler(WipeTowerHandler &wth) SceneBuilder && set_wipe_tower_handlers(std::vector<AnyPtr<WipeTowerHandler>> &&handlers)
{ {
m_wipetower_handler = &wth; m_wipetower_handlers = std::move(handlers);
return std::move(*this); return std::move(*this);
} }
SceneBuilder && set_wipe_tower_handler(AnyPtr<WipeTowerHandler> wth) SceneBuilder && set_bed_constraints(BedConstraints &&bed_constraints)
{ {
m_wipetower_handler = std::move(wth); m_bed_constraints = std::move(bed_constraints);
return std::move(*this); return std::move(*this);
} }
@ -295,13 +307,6 @@ public:
void build_arrangeable_slicer_model(ArrangeableSlicerModel &amodel); void build_arrangeable_slicer_model(ArrangeableSlicerModel &amodel);
}; };
struct MissingWipeTowerHandler : public WipeTowerHandler
{
void visit(std::function<void(Arrangeable &)>) override {}
void visit(std::function<void(const Arrangeable &)>) const override {}
void set_selection_predicate(std::function<bool()>) override {}
};
// Only a physical bed, non-zero bed index values are discarded. // Only a physical bed, non-zero bed index values are discarded.
class PhysicalOnlyVBedHandler final : public VirtualBedHandler class PhysicalOnlyVBedHandler final : public VirtualBedHandler
{ {
@ -368,29 +373,15 @@ public:
class GridStriderVBedHandler: public VirtualBedHandler class GridStriderVBedHandler: public VirtualBedHandler
{ {
// This vbed handler defines a grid of virtual beds with a large number
// of columns so that it behaves as XStrider for regular cases.
// The goal is to handle objects residing at world coordinates
// not representable with scaled coordinates. Combining XStrider with
// YStrider takes care of the X and Y axis to be mapped into the physical
// bed's coordinate region (which is representable in scaled coords)
static const int Cols;
static const int HalfCols;
static const int Offset;
XStriderVBedHandler m_xstrider; XStriderVBedHandler m_xstrider;
YStriderVBedHandler m_ystrider; YStriderVBedHandler m_ystrider;
public: public:
GridStriderVBedHandler(const BoundingBox &bedbb, GridStriderVBedHandler(const BoundingBox &bedbb, const BedsGrid::Gap &gap)
coord_t gap) : m_xstrider{bedbb, gap.x()}
: m_xstrider{bedbb, gap} , m_ystrider{bedbb, gap.y()}
, m_ystrider{bedbb, gap}
{} {}
Vec2i raw2grid(int bedidx) const;
int grid2raw(const Vec2i &crd) const;
int get_bed_index(const VBedPlaceable &obj) const override; int get_bed_index(const VBedPlaceable &obj) const override;
bool assign_bed(VBedPlaceable &inst, int bed_idx) override; bool assign_bed(VBedPlaceable &inst, int bed_idx) override;
@ -451,13 +442,15 @@ class ArrangeableModelInstance : public Arrangeable, VBedPlaceable
VBedHPtr *m_vbedh; VBedHPtr *m_vbedh;
const SelectionMask *m_selmask; const SelectionMask *m_selmask;
InstPos m_pos_within_model; InstPos m_pos_within_model;
std::optional<int> m_bed_constraint;
public: public:
explicit ArrangeableModelInstance(InstPtr *mi, explicit ArrangeableModelInstance(InstPtr *mi,
VBedHPtr *vbedh, VBedHPtr *vbedh,
const SelectionMask *selmask, const SelectionMask *selmask,
const InstPos &pos) const InstPos &pos,
: m_mi{mi}, m_vbedh{vbedh}, m_selmask{selmask}, m_pos_within_model{pos} const std::optional<int> bed_constraint)
: m_mi{mi}, m_vbedh{vbedh}, m_selmask{selmask}, m_pos_within_model{pos}, m_bed_constraint(bed_constraint)
{ {
assert(m_mi != nullptr && m_vbedh != nullptr); assert(m_mi != nullptr && m_vbedh != nullptr);
} }
@ -474,6 +467,8 @@ public:
int get_bed_index() const override { return m_vbedh->get_bed_index(*this); } int get_bed_index() const override { return m_vbedh->get_bed_index(*this); }
bool assign_bed(int bed_idx) override; bool assign_bed(int bed_idx) override;
std::optional<int> bed_constraint() const override { return m_bed_constraint; }
// VBedPlaceable: // VBedPlaceable:
BoundingBoxf bounding_box() const override { return to_2d(instance_bounding_box(*m_mi)); } BoundingBoxf bounding_box() const override { return to_2d(instance_bounding_box(*m_mi)); }
void displace(const Vec2d &transl, double rot) override void displace(const Vec2d &transl, double rot) override

View File

@ -20,14 +20,16 @@ template<class SegX = void, class SegY = void, class Pivot = void>
struct SegmentedRectangleBed { struct SegmentedRectangleBed {
Vec<2, size_t> segments = Vec<2, size_t>::Ones(); Vec<2, size_t> segments = Vec<2, size_t>::Ones();
BoundingBox bb; BoundingBox bb;
BedsGrid::Gap gap;
RectPivots pivot = RectPivots::Center; RectPivots pivot = RectPivots::Center;
SegmentedRectangleBed() = default; SegmentedRectangleBed() = default;
SegmentedRectangleBed(const BoundingBox &bb, SegmentedRectangleBed(const BoundingBox &bb,
size_t segments_x, size_t segments_x,
size_t segments_y, size_t segments_y,
const BedsGrid::Gap &gap,
const RectPivots pivot = RectPivots::Center) const RectPivots pivot = RectPivots::Center)
: segments{segments_x, segments_y}, bb{bb}, pivot{pivot} : segments{segments_x, segments_y}, bb{bb}, gap{gap}, pivot{pivot}
{} {}
size_t segments_x() const noexcept { return segments.x(); } size_t segments_x() const noexcept { return segments.x(); }
@ -41,13 +43,16 @@ struct SegmentedRectangleBed<std::integral_constant<size_t, SegX>,
std::integral_constant<size_t, SegY>> std::integral_constant<size_t, SegY>>
{ {
BoundingBox bb; BoundingBox bb;
BedsGrid::Gap gap;
RectPivots pivot = RectPivots::Center; RectPivots pivot = RectPivots::Center;
SegmentedRectangleBed() = default; SegmentedRectangleBed() = default;
explicit SegmentedRectangleBed(const BoundingBox &b, explicit SegmentedRectangleBed(const BoundingBox &b,
const BedsGrid::Gap &gap,
const RectPivots pivot = RectPivots::Center) const RectPivots pivot = RectPivots::Center)
: bb{b} : bb{b},
gap{gap}
{} {}
size_t segments_x() const noexcept { return SegX; } size_t segments_x() const noexcept { return SegX; }
@ -62,10 +67,11 @@ struct SegmentedRectangleBed<std::integral_constant<size_t, SegX>,
std::integral_constant<RectPivots, pivot>> std::integral_constant<RectPivots, pivot>>
{ {
BoundingBox bb; BoundingBox bb;
BedsGrid::Gap gap;
SegmentedRectangleBed() = default; SegmentedRectangleBed() = default;
explicit SegmentedRectangleBed(const BoundingBox &b) : bb{b} {} explicit SegmentedRectangleBed(const BoundingBox &b, const BedsGrid::Gap &gap) : bb{b}, gap{gap} {}
size_t segments_x() const noexcept { return SegX; } size_t segments_x() const noexcept { return SegX; }
size_t segments_y() const noexcept { return SegY; } size_t segments_y() const noexcept { return SegY; }
@ -92,6 +98,12 @@ auto bounding_box(const SegmentedRectangleBed<Args...> &bed)
return bed.bb; return bed.bb;
} }
template<class...Args>
auto bed_gap(const SegmentedRectangleBed<Args...> &bed)
{
return bed.gap;
}
template<class...Args> template<class...Args>
auto area(const SegmentedRectangleBed<Args...> &bed) auto area(const SegmentedRectangleBed<Args...> &bed)
{ {

View File

@ -10,6 +10,8 @@
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include "ArrangeTask.hpp" #include "ArrangeTask.hpp"
#include "libslic3r/Arrange/Items/ArrangeItem.hpp"
#include "libslic3r/SVG.hpp"
namespace Slic3r { namespace arr2 { namespace Slic3r { namespace arr2 {
@ -42,12 +44,6 @@ void extract_selected(ArrangeTask<ArrItem> &task,
<< "ObjectID " << std::to_string(arrbl.id().id) << ": " << ex.what(); << "ObjectID " << std::to_string(arrbl.id().id) << ": " << ex.what();
} }
}); });
// If the selection was empty arrange everything
if (task.printable.selected.empty() && task.unprintable.selected.empty()) {
task.printable.selected.swap(task.printable.unselected);
task.unprintable.selected.swap(task.unprintable.unselected);
}
} }
template<class ArrItem> template<class ArrItem>

View File

@ -21,8 +21,8 @@ int calculate_items_needed_to_fill_bed(const ExtendedBed &bed,
{ {
double poly_area = fixed_area(prototype_item); double poly_area = fixed_area(prototype_item);
auto area_sum_fn = [](double s, const auto &itm) { auto area_sum_fn = [&](double s, const auto &itm) {
return s + (get_bed_index(itm) == 0) * fixed_area(itm); return s + (get_bed_index(itm) == get_bed_constraint(prototype_item)) * fixed_area(itm);
}; };
double unsel_area = std::accumulate(fixed.begin(), double unsel_area = std::accumulate(fixed.begin(),
@ -82,20 +82,24 @@ void extract(FillBedTask<ArrItem> &task,
prototype_item_shrinked = itm_conv.convert(arrbl, -SCALED_EPSILON); prototype_item_shrinked = itm_conv.convert(arrbl, -SCALED_EPSILON);
}); });
const int bed_constraint{get_bed_index(*task.prototype_item)};
set_bed_constraint(*task.prototype_item, bed_constraint);
set_bed_index(*task.prototype_item, Unarranged); set_bed_index(*task.prototype_item, Unarranged);
auto collect_task_items = [&prototype_geometry_id, &task, auto collect_task_items = [&prototype_geometry_id, &task,
&itm_conv](const Arrangeable &arrbl) { &itm_conv, &bed_constraint](const Arrangeable &arrbl) {
try { try {
if (arrbl.geometry_id() == prototype_geometry_id) { if (arrbl.bed_constraint() == bed_constraint) {
if (arrbl.is_printable()) { if (arrbl.geometry_id() == prototype_geometry_id) {
auto itm = itm_conv.convert(arrbl); if (arrbl.is_printable()) {
raise_priority(itm); auto itm = itm_conv.convert(arrbl);
task.selected.emplace_back(std::move(itm)); raise_priority(itm);
task.selected.emplace_back(std::move(itm));
}
} else {
auto itm = itm_conv.convert(arrbl, -SCALED_EPSILON);
task.unselected.emplace_back(std::move(itm));
} }
} else {
auto itm = itm_conv.convert(arrbl, -SCALED_EPSILON);
task.unselected.emplace_back(std::move(itm));
} }
} catch (const EmptyItemOutlineError &ex) { } catch (const EmptyItemOutlineError &ex) {
BOOST_LOG_TRIVIAL(error) BOOST_LOG_TRIVIAL(error)
@ -170,7 +174,7 @@ std::unique_ptr<FillBedTaskResult> FillBedTask<ArrItem>::process_native(
void on_packed(ArrItem &itm) override void on_packed(ArrItem &itm) override
{ {
// Stop at the first filler that is not on the physical bed // Stop at the first filler that is not on the physical bed
do_stop = get_bed_index(itm) > PhysicalBedId && get_priority(itm) == 0; do_stop = get_bed_index(itm) > get_bed_constraint(itm) && get_priority(itm) == 0;
} }
} subctl(ctl, *this); } subctl(ctl, *this);
@ -194,12 +198,13 @@ std::unique_ptr<FillBedTaskResult> FillBedTask<ArrItem>::process_native(
auto to_add_range = Range{selected.begin() + selected_existing_count, auto to_add_range = Range{selected.begin() + selected_existing_count,
selected.end()}; selected.end()};
for (auto &itm : to_add_range) for (auto &itm : to_add_range) {
if (get_bed_index(itm) == PhysicalBedId) if (get_bed_index(itm) == get_bed_constraint(itm))
result->add_new_item(itm); result->add_new_item(itm);
}
for (auto &itm : selected_fillers) for (auto &itm : selected_fillers)
if (get_bed_index(itm) == PhysicalBedId) if (get_bed_index(itm) == get_bed_constraint(itm))
result->add_new_item(itm); result->add_new_item(itm);
return result; return result;

View File

@ -130,6 +130,16 @@ const ModelWipeTower& Model::wipe_tower() const
return wipe_tower_vector[s_multiple_beds.get_active_bed()]; return wipe_tower_vector[s_multiple_beds.get_active_bed()];
} }
const ModelWipeTower& Model::wipe_tower(const int bed_index) const
{
return wipe_tower_vector[bed_index];
}
ModelWipeTower& Model::wipe_tower(const int bed_index)
{
return wipe_tower_vector[bed_index];
}
CustomGCode::Info& Model::custom_gcode_per_print_z() CustomGCode::Info& Model::custom_gcode_per_print_z()
{ {
return const_cast<CustomGCode::Info&>(const_cast<const Model*>(this)->custom_gcode_per_print_z()); return const_cast<CustomGCode::Info&>(const_cast<const Model*>(this)->custom_gcode_per_print_z());

View File

@ -1288,9 +1288,10 @@ public:
ModelWipeTower& wipe_tower(); ModelWipeTower& wipe_tower();
const ModelWipeTower& wipe_tower() const; const ModelWipeTower& wipe_tower() const;
const ModelWipeTower& wipe_tower(const int bed_index) const;
ModelWipeTower& wipe_tower(const int bed_index);
std::vector<ModelWipeTower>& get_wipe_tower_vector() { return wipe_tower_vector; } std::vector<ModelWipeTower>& get_wipe_tower_vector() { return wipe_tower_vector; }
const std::vector<ModelWipeTower>& get_wipe_tower_vector() const { return wipe_tower_vector; } const std::vector<ModelWipeTower>& get_wipe_tower_vector() const { return wipe_tower_vector; }
CustomGCode::Info& custom_gcode_per_print_z(); CustomGCode::Info& custom_gcode_per_print_z();
const CustomGCode::Info& custom_gcode_per_print_z() const; const CustomGCode::Info& custom_gcode_per_print_z() const;

View File

@ -12,17 +12,86 @@ MultipleBeds s_multiple_beds;
bool s_reload_preview_after_switching_beds = false; bool s_reload_preview_after_switching_beds = false;
bool s_beds_just_switched = false; bool s_beds_just_switched = false;
namespace BedsGrid {
Index grid_coords_abs2index(GridCoords coords) {
coords = {std::abs(coords.x()), std::abs(coords.y())};
const int x{coords.x() + 1};
const int y{coords.y() + 1};
const int a{std::max(x, y)};
if (x == a && y == a) {
return a*a - 1;
} else if (x == a) {
return a*a - 2 * (a - 1) + coords.y() - 1;
} else {
assert(y == a);
return a*a - (a - 1) + coords.x() - 1;
}
}
const int quadrant_offset{std::numeric_limits<int>::max() / 4};
Index grid_coords2index(const GridCoords &coords) {
const int index{grid_coords_abs2index(coords)};
if (index >= quadrant_offset) {
throw std::runtime_error("Object is too far from center!");
}
if (coords.x() >= 0 && coords.y() >= 0) {
return index;
} else if (coords.x() >= 0 && coords.y() < 0) {
return quadrant_offset + index;
} else if (coords.x() < 0 && coords.y() >= 0) {
return 2*quadrant_offset + index;
} else {
return 3*quadrant_offset + index;
}
}
GridCoords index2grid_coords(Index index) {
if (index < 0) {
throw std::runtime_error{"Negative bed index cannot be translated to coords!"};
}
const int quadrant{index / quadrant_offset};
index = index % quadrant_offset;
GridCoords result{GridCoords::Zero()};
if (index == 0) {
return result;
}
int id = index;
++id;
int a = 1;
while ((a+1)*(a+1) < id)
++a;
id = id - a*a;
result.x()=a;
result.y()=a;
if (id <= a)
result.y() = id-1;
else
result.x() = id-a-1;
if (quadrant == 1) {
result.y() = -result.y();
} else if (quadrant == 2) {
result.x() = -result.x();
} else if (quadrant == 3) {
result.y() = -result.y();
result.x() = -result.x();
} else if (quadrant != 0){
throw std::runtime_error{"Impossible bed index > max int!"};
}
return result;
}
}
Vec3d MultipleBeds::get_bed_translation(int id) const Vec3d MultipleBeds::get_bed_translation(int id) const
{ {
// The x value is bed gap as multiples of the actual printable area bounding box,
// so it can be matched to how the old slicer arranged things (in SceneBuilder.cpp).
// The y value is a multiple of the larger of printable area BB and bed model BB -
// this is to make sure that the bed models do not overlap.
const double bed_gap_x = 2./10;
const double bed_gap_y = 2./10;
if (id == 0) if (id == 0)
return Vec3d::Zero(); return Vec3d::Zero();
int x = 0; int x = 0;
@ -30,28 +99,13 @@ Vec3d MultipleBeds::get_bed_translation(int id) const
if (m_layout_linear) if (m_layout_linear)
x = id; x = id;
else { else {
// Grid layout. BedsGrid::GridCoords coords{BedsGrid::index2grid_coords(id)};
++id; x = coords.x();
int a = 1; y = coords.y();
while ((a+1)*(a+1) < id)
++a;
id = id - a*a;
x=a;
y=a;
if (id <= a)
y = id-1;
else
x=id-a-1;
} }
return Vec3d(x * m_build_volume_bb_incl_model.size().x() * (1. + bed_gap_x), y * m_build_volume_bb_incl_model.size().y() * (1. + bed_gap_y), 0.); return Vec3d(x * m_build_volume_bb_incl_model.size().x() * (1. + bed_gap_x), y * m_build_volume_bb_incl_model.size().y() * (1. + bed_gap_y), 0.);
} }
void MultipleBeds::clear_inst_map() void MultipleBeds::clear_inst_map()
{ {
m_inst_to_bed.clear(); m_inst_to_bed.clear();
@ -137,6 +191,21 @@ bool MultipleBeds::is_glvolume_on_thumbnail_bed(const Model& model, int obj_idx,
return (m_bed_for_thumbnails_generation < 0 || it->second == m_bed_for_thumbnails_generation); return (m_bed_for_thumbnails_generation < 0 || it->second == m_bed_for_thumbnails_generation);
} }
void MultipleBeds::update_shown_beds(Model& model, const BuildVolume& build_volume) {
const int original_number_of_beds = m_number_of_beds;
const int stash_active = get_active_bed();
m_number_of_beds = get_max_beds();
model.update_print_volume_state(build_volume);
const int max_bed{std::accumulate(
this->m_inst_to_bed.begin(), this->m_inst_to_bed.end(), 0,
[](const int max_so_far, const std::pair<ObjectID, int> &value){
return std::max(max_so_far, value.second);
}
)};
m_number_of_beds = std::min(this->get_max_beds(), max_bed + 1);
model.update_print_volume_state(build_volume);
set_active_bed(m_number_of_beds != original_number_of_beds ? 0 : stash_active);
}
// Beware! This function is also needed for proper update of bed when normal grid project is loaded! // Beware! This function is also needed for proper update of bed when normal grid project is loaded!
bool MultipleBeds::update_after_load_or_arrange(Model& model, const BuildVolume& build_volume, std::function<void()> update_fn) bool MultipleBeds::update_after_load_or_arrange(Model& model, const BuildVolume& build_volume, std::function<void()> update_fn)
@ -214,6 +283,12 @@ bool MultipleBeds::update_after_load_or_arrange(Model& model, const BuildVolume&
} }
BedsGrid::Gap MultipleBeds::get_bed_gap() const {
const Vec2d size_with_gap{
m_build_volume_bb_incl_model.size().cwiseProduct(
Vec2d::Ones() + Vec2d{bed_gap_x, bed_gap_y})};
return scaled(Vec2d{(size_with_gap - m_build_volume_bb.size()) / 2.0});
};
void MultipleBeds::ensure_wipe_towers_on_beds(Model& model, const std::vector<std::unique_ptr<Print>>& prints) void MultipleBeds::ensure_wipe_towers_on_beds(Model& model, const std::vector<std::unique_ptr<Print>>& prints)
{ {

View File

@ -17,6 +17,14 @@ class Print;
extern bool s_reload_preview_after_switching_beds; extern bool s_reload_preview_after_switching_beds;
extern bool s_beds_just_switched; extern bool s_beds_just_switched;
namespace BedsGrid {
using GridCoords = Vec2crd;
using Index = int;
using Gap = Vec2crd;
Index grid_coords2index(const GridCoords &coords);
GridCoords index2grid_coords(Index index);
}
class MultipleBeds { class MultipleBeds {
public: public:
MultipleBeds() = default; MultipleBeds() = default;
@ -27,13 +35,14 @@ public:
void clear_inst_map(); void clear_inst_map();
void set_instance_bed(ObjectID id, int bed_idx); void set_instance_bed(ObjectID id, int bed_idx);
void inst_map_updated(); void inst_map_updated();
const std::map<ObjectID, int> &get_inst_map() const { return m_inst_to_bed; }
int get_number_of_beds() const { return m_number_of_beds; } int get_number_of_beds() const { return m_number_of_beds; }
bool should_show_next_bed() const { return m_show_next_bed; } bool should_show_next_bed() const { return m_show_next_bed; }
void request_next_bed(bool show); void request_next_bed(bool show);
int get_active_bed() const { return m_active_bed; } int get_active_bed() const { return m_active_bed; }
void set_active_bed(int i); void set_active_bed(int i);
void move_active_to_first_bed(Model& model, const BuildVolume& build_volume, bool to_or_from) const; void move_active_to_first_bed(Model& model, const BuildVolume& build_volume, bool to_or_from) const;
@ -44,21 +53,21 @@ public:
void set_last_hovered_bed(int i) { m_last_hovered_bed = i; } void set_last_hovered_bed(int i) { m_last_hovered_bed = i; }
int get_last_hovered_bed() const { return m_last_hovered_bed; } int get_last_hovered_bed() const { return m_last_hovered_bed; }
void update_shown_beds(Model& model, const BuildVolume& build_volume);
bool update_after_load_or_arrange(Model& model, const BuildVolume& build_volume, std::function<void()> update_fn); bool update_after_load_or_arrange(Model& model, const BuildVolume& build_volume, std::function<void()> update_fn);
void set_loading_project_flag(bool project) { m_loading_project = project; } void set_loading_project_flag(bool project) { m_loading_project = project; }
bool get_loading_project_flag() const { return m_loading_project; } bool get_loading_project_flag() const { return m_loading_project; }
void update_build_volume(const BoundingBoxf& build_volume_bb, const BoundingBoxf& build_volume_bb_incl_model) { m_build_volume_bb = build_volume_bb; m_build_volume_bb_incl_model = build_volume_bb_incl_model; } void update_build_volume(const BoundingBoxf& build_volume_bb, const BoundingBoxf& build_volume_bb_incl_model) {
m_build_volume_bb = build_volume_bb;
m_build_volume_bb_incl_model = build_volume_bb_incl_model;
}
BedsGrid::Gap get_bed_gap() const;
void ensure_wipe_towers_on_beds(Model& model, const std::vector<std::unique_ptr<Print>>& prints); void ensure_wipe_towers_on_beds(Model& model, const std::vector<std::unique_ptr<Print>>& prints);
private: private:
bool is_instance_on_active_bed(ObjectID id) const; bool is_instance_on_active_bed(ObjectID id) const;
int m_number_of_beds = 1; int m_number_of_beds = 1;
int m_active_bed = 0; int m_active_bed = 0;
int m_bed_for_thumbnails_generation = -1; int m_bed_for_thumbnails_generation = -1;
@ -70,13 +79,17 @@ private:
BoundingBoxf m_build_volume_bb_incl_model; BoundingBoxf m_build_volume_bb_incl_model;
bool m_layout_linear = false; bool m_layout_linear = false;
bool m_loading_project = false; bool m_loading_project = false;
// The x value is bed gap as multiples of the actual printable area bounding box,
// so it can be matched to how the old slicer arranged things (in SceneBuilder.cpp).
// The y value is a multiple of the larger of printable area BB and bed model BB -
// this is to make sure that the bed models do not overlap.
const double bed_gap_x = 2./10;
const double bed_gap_y = 2./10;
}; };
extern MultipleBeds s_multiple_beds; extern MultipleBeds s_multiple_beds;
} // namespace Slic3r } // namespace Slic3r
#endif // libslic3r_MultipleBeds_hpp_ #endif // libslic3r_MultipleBeds_hpp_

View File

@ -248,7 +248,6 @@ GLVolume::GLVolume(float r, float g, float b, float a)
, is_outside(false) , is_outside(false)
, hover(HS_None) , hover(HS_None)
, is_modifier(false) , is_modifier(false)
, is_wipe_tower(false)
, is_extrusion_path(false) , is_extrusion_path(false)
, force_native_color(false) , force_native_color(false)
, force_neutral_color(false) , force_neutral_color(false)
@ -500,11 +499,11 @@ int GLVolumeCollection::load_object_volume(
} }
#if SLIC3R_OPENGL_ES #if SLIC3R_OPENGL_ES
int GLVolumeCollection::load_wipe_tower_preview( GLVolume* GLVolumeCollection::load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, const std::vector<std::pair<float, float>>& z_and_depth_pairs, float height, float cone_angle, float pos_x, float pos_y, float width, float depth, const std::vector<std::pair<float, float>>& z_and_depth_pairs, float height, float cone_angle,
float rotation_angle, bool size_unknown, float brim_width, size_t idx, TriangleMesh* out_mesh) float rotation_angle, bool size_unknown, float brim_width, size_t idx, TriangleMesh* out_mesh)
#else #else
int GLVolumeCollection::load_wipe_tower_preview( GLVolume* GLVolumeCollection::load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, const std::vector<std::pair<float, float>>& z_and_depth_pairs, float height, float cone_angle, float pos_x, float pos_y, float width, float depth, const std::vector<std::pair<float, float>>& z_and_depth_pairs, float height, float cone_angle,
float rotation_angle, bool size_unknown, float brim_width, size_t idx) float rotation_angle, bool size_unknown, float brim_width, size_t idx)
#endif // SLIC3R_OPENGL_ES #endif // SLIC3R_OPENGL_ES
@ -591,9 +590,8 @@ int GLVolumeCollection::load_wipe_tower_preview(
mesh.merge(cone_mesh); mesh.merge(cone_mesh);
} }
GLVolume* result{new GLVolume(color)};
volumes.emplace_back(new GLVolume(color)); GLVolume& v = *result;
GLVolume& v = *volumes.back();
#if SLIC3R_OPENGL_ES #if SLIC3R_OPENGL_ES
if (out_mesh != nullptr) if (out_mesh != nullptr)
*out_mesh = mesh; *out_mesh = mesh;
@ -607,9 +605,10 @@ int GLVolumeCollection::load_wipe_tower_preview(
v.composite_id = GLVolume::CompositeID(INT_MAX - idx, 0, 0); v.composite_id = GLVolume::CompositeID(INT_MAX - idx, 0, 0);
v.geometry_id.first = 0; v.geometry_id.first = 0;
v.geometry_id.second = wipe_tower_instance_id(idx).id; v.geometry_id.second = wipe_tower_instance_id(idx).id;
v.is_wipe_tower = true; v.wipe_tower_bed_index = idx;
v.shader_outside_printer_detection_enabled = !size_unknown; v.shader_outside_printer_detection_enabled = !size_unknown;
return int(volumes.size() - 1);
return result;
} }
// Load SLA auxiliary GLVolumes (for support trees or pad). // Load SLA auxiliary GLVolumes (for support trees or pad).
@ -803,7 +802,7 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
const int obj_idx = volume.first->object_idx(); const int obj_idx = volume.first->object_idx();
const int vol_idx = volume.first->volume_idx(); const int vol_idx = volume.first->volume_idx();
const bool render_as_mmu_painted = is_render_as_mmu_painted_enabled && !volume.first->selected && const bool render_as_mmu_painted = is_render_as_mmu_painted_enabled && !volume.first->selected &&
!volume.first->is_outside && volume.first->hover == GLVolume::HS_None && !volume.first->is_wipe_tower && obj_idx >= 0 && vol_idx >= 0 && !volume.first->is_outside && volume.first->hover == GLVolume::HS_None && !volume.first->is_wipe_tower() && obj_idx >= 0 && vol_idx >= 0 &&
!model_objects[obj_idx]->volumes[vol_idx]->mm_segmentation_facets.empty() && !model_objects[obj_idx]->volumes[vol_idx]->mm_segmentation_facets.empty() &&
type != GLVolumeCollection::ERenderType::Transparent; // to filter out shells (not very nice) type != GLVolumeCollection::ERenderType::Transparent; // to filter out shells (not very nice)
volume.first->set_render_color(true); volume.first->set_render_color(true);
@ -882,7 +881,7 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
shader->set_uniform("print_volume.xy_data", m_print_volume.data); shader->set_uniform("print_volume.xy_data", m_print_volume.data);
shader->set_uniform("print_volume.z_data", m_print_volume.zs); shader->set_uniform("print_volume.z_data", m_print_volume.zs);
shader->set_uniform("volume_world_matrix", world_matrix); shader->set_uniform("volume_world_matrix", world_matrix);
shader->set_uniform("slope.actived", m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower); shader->set_uniform("slope.actived", m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower());
shader->set_uniform("slope.volume_world_normal_matrix", static_cast<Matrix3f>(world_matrix_inv_transp.cast<float>())); shader->set_uniform("slope.volume_world_normal_matrix", static_cast<Matrix3f>(world_matrix_inv_transp.cast<float>()));
shader->set_uniform("slope.normal_z", m_slope.normal_z); shader->set_uniform("slope.normal_z", m_slope.normal_z);
@ -1012,7 +1011,7 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con
} }
for (GLVolume* volume : volumes) { for (GLVolume* volume : volumes) {
if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || volume->is_sla_pad() || volume->is_sla_support()) if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower() || volume->is_sla_pad() || volume->is_sla_support())
continue; continue;
int extruder_id = volume->extruder_id - 1; int extruder_id = volume->extruder_id - 1;

View File

@ -189,8 +189,6 @@ public:
bool is_outside : 1; bool is_outside : 1;
// Wheter or not this volume has been generated from a modifier // Wheter or not this volume has been generated from a modifier
bool is_modifier : 1; bool is_modifier : 1;
// Wheter or not this volume has been generated from the wipe tower
bool is_wipe_tower : 1;
// Wheter or not this volume has been generated from an extrusion path // Wheter or not this volume has been generated from an extrusion path
bool is_extrusion_path : 1; bool is_extrusion_path : 1;
// Whether or not always use the volume's own color (not using SELECTED/HOVER/DISABLED/OUTSIDE) // Whether or not always use the volume's own color (not using SELECTED/HOVER/DISABLED/OUTSIDE)
@ -216,6 +214,13 @@ public:
// Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer. // Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer.
std::vector<size_t> offsets; std::vector<size_t> offsets;
std::optional<int> wipe_tower_bed_index;
// Wheter or not this volume has been generated from the wipe tower
bool is_wipe_tower() const {
return bool{wipe_tower_bed_index};
}
// Bounding box of this volume, in unscaled coordinates. // Bounding box of this volume, in unscaled coordinates.
BoundingBoxf3 bounding_box() const { BoundingBoxf3 bounding_box() const {
return this->model.get_bounding_box(); return this->model.get_bounding_box();
@ -425,10 +430,10 @@ public:
int instance_idx); int instance_idx);
#if SLIC3R_OPENGL_ES #if SLIC3R_OPENGL_ES
int load_wipe_tower_preview( GLVolume* load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, const std::vector<std::pair<float, float>>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, size_t idx, TriangleMesh* out_mesh = nullptr); float pos_x, float pos_y, float width, float depth, const std::vector<std::pair<float, float>>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, size_t idx, TriangleMesh* out_mesh = nullptr);
#else #else
int load_wipe_tower_preview( GLVolume* load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, const std::vector<std::pair<float, float>>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, size_t idx); float pos_x, float pos_y, float width, float depth, const std::vector<std::pair<float, float>>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, size_t idx);
#endif // SLIC3R_OPENGL_ES #endif // SLIC3R_OPENGL_ES

View File

@ -34,7 +34,7 @@ ArrangeSettingsDialogImgui::ArrangeSettingsDialogImgui(
: m_imgui{imgui}, m_db{std::move(db)} : m_imgui{imgui}, m_db{std::move(db)}
{} {}
void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y) void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y, bool current_bed)
{ {
assert(m_imgui && m_db); assert(m_imgui && m_db);
@ -131,9 +131,12 @@ void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y)
ImGui::SameLine(); ImGui::SameLine();
if (ImGuiPureWrap::button(_u8L("Arrange")) && m_on_arrange_btn) { if (!current_bed && ImGuiPureWrap::button(_u8L("Arrange")) && m_on_arrange_btn) {
m_on_arrange_btn(); m_on_arrange_btn();
} }
if (current_bed && ImGuiPureWrap::button(_u8L("Arrange bed")) && m_on_arrange_bed_btn) {
m_on_arrange_bed_btn();
}
ImGuiPureWrap::end(); ImGuiPureWrap::end();
} }

View File

@ -17,6 +17,7 @@ class ArrangeSettingsDialogImgui: public arr2::ArrangeSettingsView {
AnyPtr<arr2::ArrangeSettingsDb> m_db; AnyPtr<arr2::ArrangeSettingsDb> m_db;
std::function<void()> m_on_arrange_btn; std::function<void()> m_on_arrange_btn;
std::function<void()> m_on_arrange_bed_btn;
std::function<void()> m_on_reset_btn; std::function<void()> m_on_reset_btn;
std::function<bool()> m_show_xl_combo_predicate = [] { return true; }; std::function<bool()> m_show_xl_combo_predicate = [] { return true; };
@ -24,7 +25,7 @@ class ArrangeSettingsDialogImgui: public arr2::ArrangeSettingsView {
public: public:
ArrangeSettingsDialogImgui(ImGuiWrapper *imgui, AnyPtr<arr2::ArrangeSettingsDb> db); ArrangeSettingsDialogImgui(ImGuiWrapper *imgui, AnyPtr<arr2::ArrangeSettingsDb> db);
void render(float pos_x, float pos_y); void render(float pos_x, float pos_y, bool current_bed);
void show_xl_align_combo(std::function<bool()> pred) void show_xl_align_combo(std::function<bool()> pred)
{ {
@ -36,6 +37,11 @@ public:
m_on_arrange_btn = on_arrangefn; m_on_arrange_btn = on_arrangefn;
} }
void on_arrange_bed_btn(std::function<void()> on_arrangefn)
{
m_on_arrange_bed_btn = on_arrangefn;
}
void on_reset_btn(std::function<void()> on_resetfn) void on_reset_btn(std::function<void()> on_resetfn)
{ {
m_on_reset_btn = on_resetfn; m_on_reset_btn = on_resetfn;

View File

@ -1698,9 +1698,9 @@ void GCodeViewer::load_wipetower_shell(const Print& print)
const std::vector<std::pair<float, float>> z_and_depth_pairs = print.wipe_tower_data(extruders_count).z_and_depth_pairs; const std::vector<std::pair<float, float>> z_and_depth_pairs = print.wipe_tower_data(extruders_count).z_and_depth_pairs;
const float brim_width = wipe_tower_data.brim_width; const float brim_width = wipe_tower_data.brim_width;
if (depth != 0.) { if (depth != 0.) {
m_shells.volumes.load_wipe_tower_preview(wxGetApp().plater()->model().wipe_tower().position.x(), wxGetApp().plater()->model().wipe_tower().position.y(), config.wipe_tower_width, depth, z_and_depth_pairs, GLVolume* volume{m_shells.volumes.load_wipe_tower_preview(wxGetApp().plater()->model().wipe_tower().position.x(), wxGetApp().plater()->model().wipe_tower().position.y(), config.wipe_tower_width, depth, z_and_depth_pairs,
max_z, config.wipe_tower_cone_angle, wxGetApp().plater()->model().wipe_tower().rotation, false, brim_width, 0); max_z, config.wipe_tower_cone_angle, wxGetApp().plater()->model().wipe_tower().rotation, false, brim_width, 0)};
GLVolume* volume = m_shells.volumes.volumes.back(); m_shells.volumes.volumes.emplace_back(volume);
volume->color.a(0.25f); volume->color.a(0.25f);
volume->force_native_color = true; volume->force_native_color = true;
volume->set_render_color(true); volume->set_render_color(true);

View File

@ -1032,6 +1032,7 @@ wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE_CURRENT_BED, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event<int>); wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event<int>);
@ -1318,7 +1319,18 @@ PrinterTechnology GLCanvas3D::current_printer_technology() const
bool GLCanvas3D::is_arrange_alignment_enabled() const bool GLCanvas3D::is_arrange_alignment_enabled() const
{ {
return m_config ? is_XL_printer(*m_config) && !this->get_wipe_tower_info() : false; if (m_config == nullptr) {
return false;
}
if (!is_XL_printer(*m_config)) {
return false;
}
for (const WipeTowerInfo &wti : this->get_wipe_tower_infos()) {
if (bool{wti}) {
return false;
}
}
return true;
} }
GLCanvas3D::GLCanvas3D(wxGLCanvas *canvas, Bed3D &bed) GLCanvas3D::GLCanvas3D(wxGLCanvas *canvas, Bed3D &bed)
@ -1371,6 +1383,9 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas *canvas, Bed3D &bed)
m_arrange_settings_dialog.on_arrange_btn([]{ m_arrange_settings_dialog.on_arrange_btn([]{
wxGetApp().plater()->arrange(); wxGetApp().plater()->arrange();
}); });
m_arrange_settings_dialog.on_arrange_bed_btn([]{
wxGetApp().plater()->arrange_current_bed();
});
} }
GLCanvas3D::~GLCanvas3D() GLCanvas3D::~GLCanvas3D()
@ -1491,7 +1506,7 @@ bool GLCanvas3D::check_volumes_outside_state(GLVolumeCollection& volumes, ModelI
const std::vector<unsigned int> volumes_idxs = volumes_to_process_idxs(); const std::vector<unsigned int> volumes_idxs = volumes_to_process_idxs();
for (unsigned int vol_idx : volumes_idxs) { for (unsigned int vol_idx : volumes_idxs) {
GLVolume* volume = volumes.volumes[vol_idx]; GLVolume* volume = volumes.volumes[vol_idx];
if (!volume->is_modifier && (volume->shader_outside_printer_detection_enabled || (!volume->is_wipe_tower && volume->composite_id.volume_id >= 0))) { if (!volume->is_modifier && (volume->shader_outside_printer_detection_enabled || (!volume->is_wipe_tower() && volume->composite_id.volume_id >= 0))) {
BuildVolume::ObjectState state; BuildVolume::ObjectState state;
int bed_idx = -1; int bed_idx = -1;
if (volume_below(*volume)) if (volume_below(*volume))
@ -1571,7 +1586,7 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject
{ {
std::vector<std::shared_ptr<SceneRaycasterItem>>* raycasters = get_raycasters_for_picking(SceneRaycaster::EType::Volume); std::vector<std::shared_ptr<SceneRaycasterItem>>* raycasters = get_raycasters_for_picking(SceneRaycaster::EType::Volume);
for (GLVolume* vol : m_volumes.volumes) { for (GLVolume* vol : m_volumes.volumes) {
if (vol->is_wipe_tower) if (vol->is_wipe_tower())
vol->is_active = (visible && mo == nullptr); vol->is_active = (visible && mo == nullptr);
else { else {
if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
@ -2283,7 +2298,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
instance_ids_selected.emplace_back(volume->geometry_id.second); instance_ids_selected.emplace_back(volume->geometry_id.second);
if (mvs == nullptr || force_full_scene_refresh) { if (mvs == nullptr || force_full_scene_refresh) {
// This GLVolume will be released. // This GLVolume will be released.
if (volume->is_wipe_tower) { if (volume->is_wipe_tower()) {
#if SLIC3R_OPENGL_ES #if SLIC3R_OPENGL_ES
m_wipe_tower_meshes.clear(); m_wipe_tower_meshes.clear();
#endif // SLIC3R_OPENGL_ES #endif // SLIC3R_OPENGL_ES
@ -2454,9 +2469,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
const float ca = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_cone_angle"))->value; const float ca = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_cone_angle"))->value;
if (extruders_count > 1 && wt && !co) { if (extruders_count > 1 && wt && !co) {
for (size_t bed_idx = 0; bed_idx < s_multiple_beds.get_max_beds(); ++bed_idx) {
for (size_t bed_idx = 0; bed_idx < s_multiple_beds.get_number_of_beds(); ++bed_idx) {
const Print *print = wxGetApp().plater()->get_fff_prints()[bed_idx].get(); const Print *print = wxGetApp().plater()->get_fff_prints()[bed_idx].get();
const float x = m_model->get_wipe_tower_vector()[bed_idx].position.x(); const float x = m_model->get_wipe_tower_vector()[bed_idx].position.x();
@ -2471,24 +2484,36 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
const double height = height_real < 0.f ? std::max(m_model->max_z(), 10.0) : height_real; const double height = height_real < 0.f ? std::max(m_model->max_z(), 10.0) : height_real;
if (depth != 0.) { if (depth != 0.) {
#if SLIC3R_OPENGL_ES #if SLIC3R_OPENGL_ES
if (bed_idx >= m_wipe_tower_meshes.size()) if (bed_idx >= m_wipe_tower_meshes.size())
m_wipe_tower_meshes.resize(bed_idx + 1); m_wipe_tower_meshes.resize(bed_idx + 1);
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( GLVolume* volume = m_volumes.load_wipe_tower_preview(
x, y, w, depth, z_and_depth_pairs, (float)height, ca, a, !is_wipe_tower_step_done, x, y, w, depth, z_and_depth_pairs, (float)height, ca, a, !is_wipe_tower_step_done,
bw, bed_idx, &m_wipe_tower_meshes[bed_idx]); bw, bed_idx, &m_wipe_tower_meshes[bed_idx]);
#else #else
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( GLVolume* volume = m_volumes.load_wipe_tower_preview(
x, y, w, depth, z_and_depth_pairs, (float)height, ca, a, !is_wipe_tower_step_done, x, y, w, depth, z_and_depth_pairs, (float)height, ca, a, !is_wipe_tower_step_done,
bw, bed_idx); bw, bed_idx);
#endif // SLIC3R_OPENGL_ES #endif // SLIC3R_OPENGL_ES
const BoundingBoxf3& bb = volume->bounding_box();
m_wipe_tower_bounding_boxes[bed_idx] = BoundingBoxf{to_2d(bb.min), to_2d(bb.max)};
if(bed_idx < s_multiple_beds.get_number_of_beds()) {
m_volumes.volumes.emplace_back(volume);
const auto volume_idx_wipe_tower_new{static_cast<int>(m_volumes.volumes.size() - 1)};
auto it = volume_idxs_wipe_towers_old.find(m_volumes.volumes.back()->geometry_id.second); auto it = volume_idxs_wipe_towers_old.find(m_volumes.volumes.back()->geometry_id.second);
if (it != volume_idxs_wipe_towers_old.end()) if (it != volume_idxs_wipe_towers_old.end())
map_glvolume_old_to_new[it->second] = volume_idx_wipe_tower_new; map_glvolume_old_to_new[it->second] = volume_idx_wipe_tower_new;
m_volumes.volumes.back()->set_volume_offset(m_volumes.volumes.back()->get_volume_offset() + s_multiple_beds.get_bed_translation(bed_idx)); m_volumes.volumes.back()->set_volume_offset(m_volumes.volumes.back()->get_volume_offset() + s_multiple_beds.get_bed_translation(bed_idx));
} else {
delete volume;
} }
}
} }
s_multiple_beds.ensure_wipe_towers_on_beds(wxGetApp().plater()->model(), wxGetApp().plater()->get_fff_prints()); s_multiple_beds.ensure_wipe_towers_on_beds(wxGetApp().plater()->model(), wxGetApp().plater()->get_fff_prints());
} else {
m_wipe_tower_bounding_boxes.fill(std::nullopt);
} }
} else {
m_wipe_tower_bounding_boxes.fill(std::nullopt);
} }
update_volumes_colors_by_extruder(); update_volumes_colors_by_extruder();
@ -2917,6 +2942,8 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
case 'b': { zoom_to_bed(); break; } case 'b': { zoom_to_bed(); break; }
case 'C': case 'C':
case 'c': { m_gcode_viewer.toggle_gcode_window_visibility(); m_dirty = true; request_extra_frame(); break; } case 'c': { m_gcode_viewer.toggle_gcode_window_visibility(); m_dirty = true; request_extra_frame(); break; }
case 'D':
case 'd': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE_CURRENT_BED)); break; }
case 'E': case 'E':
case 'e': { m_labels.show(!m_labels.is_shown()); m_dirty = true; break; } case 'e': { m_labels.show(!m_labels.is_shown()); m_dirty = true; break; }
case 'G': case 'G':
@ -3834,7 +3861,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (!m_hover_volume_idxs.empty()) { if (!m_hover_volume_idxs.empty()) {
// if right clicking on volume, propagate event through callback (shows context menu) // if right clicking on volume, propagate event through callback (shows context menu)
int volume_idx = get_first_hover_volume_idx(); int volume_idx = get_first_hover_volume_idx();
if (!m_volumes.volumes[volume_idx]->is_wipe_tower // no context menu for the wipe tower if (!m_volumes.volumes[volume_idx]->is_wipe_tower() // no context menu for the wipe tower
&& (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports && m_gizmos.get_current_type() != GLGizmosManager::Measure)) // disable context menu when the gizmo is open && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports && m_gizmos.get_current_type() != GLGizmosManager::Measure)) // disable context menu when the gizmo is open
{ {
// forces the selection of the volume // forces the selection of the volume
@ -3859,7 +3886,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (!m_mouse.dragging) { if (!m_mouse.dragging) {
// do not post the event if the user is panning the scene // do not post the event if the user is panning the scene
// or if right click was done over the wipe tower // or if right click was done over the wipe tower
const bool post_right_click_event = (m_hover_volume_idxs.empty() || !m_volumes.volumes[get_first_hover_volume_idx()]->is_wipe_tower) && const bool post_right_click_event = (m_hover_volume_idxs.empty() || !m_volumes.volumes[get_first_hover_volume_idx()]->is_wipe_tower()) &&
m_gizmos.get_current_type() != GLGizmosManager::Measure; m_gizmos.get_current_type() != GLGizmosManager::Measure;
if (post_right_click_event) if (post_right_click_event)
post_event(RBtnEvent(EVT_GLCANVAS_RIGHT_CLICK, { logical_pos, m_hover_volume_idxs.empty() })); post_event(RBtnEvent(EVT_GLCANVAS_RIGHT_CLICK, { logical_pos, m_hover_volume_idxs.empty() }));
@ -4020,7 +4047,7 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
model_object->invalidate_bounding_box(); model_object->invalidate_bounding_box();
} }
} }
else if (m_selection.is_wipe_tower() && v->is_wipe_tower && m_selection.contains_volume(vol_id)) { else if (m_selection.is_wipe_tower() && v->is_wipe_tower() && m_selection.contains_volume(vol_id)) {
// Move a wipe tower proxy. // Move a wipe tower proxy.
for (size_t bed_idx = 0; bed_idx < s_multiple_beds.get_max_beds(); ++bed_idx) { for (size_t bed_idx = 0; bed_idx < s_multiple_beds.get_max_beds(); ++bed_idx) {
if (v->geometry_id.second == wipe_tower_instance_id(bed_idx).id) { if (v->geometry_id.second == wipe_tower_instance_id(bed_idx).id) {
@ -4101,7 +4128,7 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
for (const GLVolume* v : m_volumes.volumes) { for (const GLVolume* v : m_volumes.volumes) {
++v_id; ++v_id;
if (v->is_wipe_tower) { if (v->is_wipe_tower()) {
if (m_selection.contains_volume(v_id)) { if (m_selection.contains_volume(v_id)) {
for (size_t bed_idx = 0; bed_idx < s_multiple_beds.get_max_beds(); ++bed_idx) { for (size_t bed_idx = 0; bed_idx < s_multiple_beds.get_max_beds(); ++bed_idx) {
if (v->geometry_id.second == wipe_tower_instance_id(bed_idx).id) { if (v->geometry_id.second == wipe_tower_instance_id(bed_idx).id) {
@ -4424,22 +4451,23 @@ void GLCanvas3D::update_ui_from_settings()
wxGetApp().plater()->enable_collapse_toolbar(wxGetApp().app_config->get_bool("show_collapse_button") || !wxGetApp().sidebar().IsShown()); wxGetApp().plater()->enable_collapse_toolbar(wxGetApp().app_config->get_bool("show_collapse_button") || !wxGetApp().sidebar().IsShown());
} }
GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const std::vector<GLCanvas3D::WipeTowerInfo> GLCanvas3D::get_wipe_tower_infos() const
{ {
WipeTowerInfo wti; std::vector<WipeTowerInfo> result;
for (const GLVolume* vol : m_volumes.volumes) { for (size_t bed_idx = 0; bed_idx < s_multiple_beds.get_max_beds(); ++bed_idx) {
if (vol->is_wipe_tower) { if (m_wipe_tower_bounding_boxes[bed_idx]) {
wti.m_pos = Vec2d(m_model->wipe_tower().position.x(), const ModelWipeTower &wipe_tower{m_model->wipe_tower(bed_idx)};
m_model->wipe_tower().position.y()); WipeTowerInfo wti;
wti.m_rotation = (M_PI/180.) * m_model->wipe_tower().rotation; wti.m_pos = Vec2d(wipe_tower.position.x(), wipe_tower.position.y());
const BoundingBoxf3& bb = vol->bounding_box(); wti.m_rotation = (M_PI/180.) * wipe_tower.rotation;
wti.m_bb = BoundingBoxf{to_2d(bb.min), to_2d(bb.max)}; wti.m_bb = *m_wipe_tower_bounding_boxes[bed_idx];
break; wti.m_bed_index = bed_idx;
result.push_back(std::move(wti));
} }
} }
return wti; return result;
} }
Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos) Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos)
@ -4536,7 +4564,7 @@ void GLCanvas3D::update_sequential_clearance(bool force_contours_generation)
// second: fill temporary cache with data from volumes // second: fill temporary cache with data from volumes
for (const GLVolume* v : m_volumes.volumes) { for (const GLVolume* v : m_volumes.volumes) {
if (v->is_wipe_tower) if (v->is_wipe_tower())
continue; continue;
const int object_idx = v->object_idx(); const int object_idx = v->object_idx();
@ -4713,9 +4741,9 @@ bool GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x)
return action_taken; return action_taken;
} }
bool GLCanvas3D::_render_arrange_menu(float pos_x) bool GLCanvas3D::_render_arrange_menu(float pos_x, bool current_bed)
{ {
m_arrange_settings_dialog.render(pos_x, m_main_toolbar.get_height()); m_arrange_settings_dialog.render(pos_x, m_main_toolbar.get_height(), current_bed);
return true; return true;
} }
@ -4730,7 +4758,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const
GLVolumePtrs visible_volumes; GLVolumePtrs visible_volumes;
for (GLVolume* vol : volumes.volumes) { for (GLVolume* vol : volumes.volumes) {
if (!vol->is_modifier && !vol->is_wipe_tower && (!thumbnail_params.parts_only || vol->composite_id.volume_id >= 0)) { if (!vol->is_modifier && !vol->is_wipe_tower() && (!thumbnail_params.parts_only || vol->composite_id.volume_id >= 0)) {
if (!thumbnail_params.printable_only || is_visible(*vol)) { if (!thumbnail_params.printable_only || is_visible(*vol)) {
if (s_multiple_beds.is_glvolume_on_thumbnail_bed(wxGetApp().model(), vol->composite_id.object_id, vol->composite_id.instance_id)) if (s_multiple_beds.is_glvolume_on_thumbnail_bed(wxGetApp().model(), vol->composite_id.object_id, vol->composite_id.instance_id))
visible_volumes.emplace_back(vol); visible_volumes.emplace_back(vol);
@ -5161,7 +5189,23 @@ bool GLCanvas3D::_init_main_toolbar()
item.right.toggable = true; item.right.toggable = true;
item.right.render_callback = [this](float left, float right, float, float) { item.right.render_callback = [this](float left, float right, float, float) {
if (m_canvas != nullptr) if (m_canvas != nullptr)
_render_arrange_menu(0.5f * (left + right)); _render_arrange_menu(0.5f * (left + right), false);
};
if (!m_main_toolbar.add_item(item))
return false;
item.name = "arrangecurrent";
item.icon_filename = "arrange_current.svg";
item.tooltip =
_u8L("Arrange current bed") + " [D]\n"
+ _u8L("Arrange selection on current bed") + " [Shift+D]\n";
item.sprite_id = sprite_id++;
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE_CURRENT_BED)); };
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); };
item.right.toggable = true;
item.right.render_callback = [this](float left, float right, float, float) {
if (m_canvas != nullptr)
_render_arrange_menu(0.5f * (left + right), true);
}; };
if (!m_main_toolbar.add_item(item)) if (!m_main_toolbar.add_item(item))
return false; return false;
@ -6980,6 +7024,11 @@ bool GLCanvas3D::_deactivate_arrange_menu()
return true; return true;
} }
if (m_main_toolbar.is_item_pressed("arrangecurrent")) {
m_main_toolbar.force_right_action(m_main_toolbar.get_item_id("arrangecurrent"), *this);
return true;
}
return false; return false;
} }
@ -7022,10 +7071,10 @@ const SLAPrint* GLCanvas3D::sla_print() const
return (m_process == nullptr) ? nullptr : m_process->sla_print(); return (m_process == nullptr) ? nullptr : m_process->sla_print();
} }
void GLCanvas3D::WipeTowerInfo::apply_wipe_tower(Vec2d pos, double rot) void GLCanvas3D::WipeTowerInfo::apply_wipe_tower(Vec2d pos, double rot, int bed_index)
{ {
wxGetApp().plater()->model().wipe_tower().position = pos; wxGetApp().plater()->model().wipe_tower(bed_index).position = pos;
wxGetApp().plater()->model().wipe_tower().rotation = (180. / M_PI) * rot; wxGetApp().plater()->model().wipe_tower(bed_index).rotation = (180. / M_PI) * rot;
} }
void GLCanvas3D::RenderTimer::Notify() void GLCanvas3D::RenderTimer::Notify()

View File

@ -158,6 +158,7 @@ wxDECLARE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent); wxDECLARE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_ARRANGE_CURRENT_BED, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event<int>); // data: +1 => increase, -1 => decrease wxDECLARE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event<int>); // data: +1 => increase, -1 => decrease
@ -512,6 +513,8 @@ private:
#if SLIC3R_OPENGL_ES #if SLIC3R_OPENGL_ES
std::vector<TriangleMesh> m_wipe_tower_meshes; std::vector<TriangleMesh> m_wipe_tower_meshes;
#endif // SLIC3R_OPENGL_ES #endif // SLIC3R_OPENGL_ES
std::array<std::optional<BoundingBoxf>, MAX_NUMBER_OF_BEDS> m_wipe_tower_bounding_boxes;
GCodeViewer m_gcode_viewer; GCodeViewer m_gcode_viewer;
RenderTimer m_render_timer; RenderTimer m_render_timer;
@ -904,28 +907,30 @@ public:
int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; } int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; }
int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); } int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); }
void set_selected_extruder(int extruder) { m_selected_extruder = extruder;} void set_selected_extruder(int extruder) { m_selected_extruder = extruder;}
class WipeTowerInfo { class WipeTowerInfo {
protected: protected:
Vec2d m_pos = {NaNd, NaNd}; Vec2d m_pos = {NaNd, NaNd};
double m_rotation = 0.; double m_rotation = 0.;
BoundingBoxf m_bb; BoundingBoxf m_bb;
int m_bed_index{0};
friend class GLCanvas3D; friend class GLCanvas3D;
public: public:
inline operator bool() const { inline operator bool() const {
return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y()); return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y());
} }
inline const Vec2d& pos() const { return m_pos; } inline const Vec2d& pos() const { return m_pos; }
inline double rotation() const { return m_rotation; } inline double rotation() const { return m_rotation; }
inline const Vec2d bb_size() const { return m_bb.size(); } inline const Vec2d bb_size() const { return m_bb.size(); }
inline const BoundingBoxf& bounding_box() const { return m_bb; } inline const BoundingBoxf& bounding_box() const { return m_bb; }
inline const int bed_index() const { return m_bed_index; }
static void apply_wipe_tower(Vec2d pos, double rot); static void apply_wipe_tower(Vec2d pos, double rot, int bed_index);
}; };
WipeTowerInfo get_wipe_tower_info() const; std::vector<WipeTowerInfo> get_wipe_tower_infos() const;
// Returns the view ray line, in world coordinate, at the given mouse position. // Returns the view ray line, in world coordinate, at the given mouse position.
Linef3 mouse_ray(const Point& mouse_pos); Linef3 mouse_ray(const Point& mouse_pos);
@ -1072,7 +1077,7 @@ private:
void _render_sla_slices(); void _render_sla_slices();
void _render_selection_sidebar_hints() { m_selection.render_sidebar_hints(m_sidebar_field); } void _render_selection_sidebar_hints() { m_selection.render_sidebar_hints(m_sidebar_field); }
bool _render_undo_redo_stack(const bool is_undo, float pos_x); bool _render_undo_redo_stack(const bool is_undo, float pos_x);
bool _render_arrange_menu(float pos_x); bool _render_arrange_menu(float pos_x, bool current_bed);
void _render_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type); void _render_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type);
// render thumbnail using an off-screen framebuffer // render thumbnail using an off-screen framebuffer
void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type); void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type);

View File

@ -25,6 +25,7 @@ wxDEFINE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_ARRANGE_CURRENT_BED, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_COPY, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_COPY, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_PASTE, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_PASTE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent);

View File

@ -24,6 +24,7 @@ wxDECLARE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_ARRANGE_CURRENT_BED, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_COPY, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_COPY, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_PASTE, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_PASTE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent);

View File

@ -28,9 +28,13 @@ class GUISelectionMask: public arr2::SelectionMask {
public: public:
explicit GUISelectionMask(const Selection *sel) : m_sel{sel} {} explicit GUISelectionMask(const Selection *sel) : m_sel{sel} {}
bool is_wipe_tower() const override bool is_wipe_tower_selected(int wipe_tower_index) const override
{ {
return m_sel->is_wipe_tower(); const GLVolume *volume{GUI::get_selected_gl_volume(*m_sel)};
if (volume != nullptr && volume->wipe_tower_bed_index == wipe_tower_index) {
return true;
}
return false;
} }
std::vector<bool> selected_objects() const override std::vector<bool> selected_objects() const override
@ -69,6 +73,91 @@ public:
} }
}; };
class BedSelectionMask: public arr2::SelectionMask {
const int m_bed_index;
std::vector<std::vector<bool>> m_selected_instances;
std::vector<bool> m_selected_objects;
public:
explicit BedSelectionMask(const int bed_index, const ModelObjectPtrs &objects, const std::set<ObjectID> &instances_on_bed):
m_bed_index{bed_index},
m_selected_instances(get_selected_instances(objects, instances_on_bed)),
m_selected_objects(get_selected_objects(this->m_selected_instances))
{}
bool is_wipe_tower_selected(int wipe_tower_index) const override
{
return wipe_tower_index == m_bed_index;
}
std::vector<bool> selected_objects() const override
{
return this->m_selected_objects;
}
std::vector<bool> selected_instances(int obj_id) const override {
return this->m_selected_instances[obj_id];
}
private:
static std::vector<bool> get_selected_objects(
const std::vector<std::vector<bool>> &selected_instances
) {
std::vector<bool> result;
std::transform(
selected_instances.begin(),
selected_instances.end(),
std::back_inserter(result),
[&](const std::vector<bool> &object_selected_instances) {
return std::any_of(
object_selected_instances.begin(),
object_selected_instances.end(),
[](const bool is_selected){ return is_selected; }
);
}
);
return result;
}
std::vector<bool> get_selected_instances(
const ModelObject &object,
const std::set<ObjectID> &instances_on_bed
) {
std::vector<bool> result;
std::transform(
object.instances.begin(),
object.instances.end(),
std::back_inserter(result),
[&](const ModelInstance *instance) {
const auto instance_bed_index{instances_on_bed.find(instance->id())};
if(instance_bed_index != instances_on_bed.end()) {
return true;
}
return false;
}
);
return result;
}
std::vector<std::vector<bool>> get_selected_instances(
const ModelObjectPtrs &objects,
const std::set<ObjectID> &instances_on_bed
) {
std::vector<std::vector<bool>> result;
std::transform(
objects.begin(),
objects.end(),
std::back_inserter(result),
[&](const ModelObject *object){
return get_selected_instances(*object, instances_on_bed);
}
);
return result;
}
};
static Polygon get_wtpoly(const GLCanvas3D::WipeTowerInfo &wti) static Polygon get_wtpoly(const GLCanvas3D::WipeTowerInfo &wti)
{ {
@ -97,9 +186,9 @@ class ArrangeableWT: public arr2::ArrangeableWipeTowerBase
public: public:
explicit ArrangeableWT(const ObjectID &oid, explicit ArrangeableWT(const ObjectID &oid,
const GLCanvas3D::WipeTowerInfo &wti, const GLCanvas3D::WipeTowerInfo &wti,
std::function<bool()> sel_pred, std::function<bool(int)> sel_pred,
const BoundingBox xl_bb = {}) const BoundingBox xl_bb = {})
: arr2::ArrangeableWipeTowerBase{oid, get_wtpoly(wti), std::move(sel_pred)} : arr2::ArrangeableWipeTowerBase{oid, get_wtpoly(wti), wti.bed_index(), std::move(sel_pred)}
, m_orig_tr{wti.pos()} , m_orig_tr{wti.pos()}
, m_orig_rot{wti.rotation()} , m_orig_rot{wti.rotation()}
, m_xl_bb{xl_bb} , m_xl_bb{xl_bb}
@ -108,7 +197,7 @@ public:
// Rotation is disabled for wipe tower in arrangement // Rotation is disabled for wipe tower in arrangement
void transform(const Vec2d &transl, double /*rot*/) override void transform(const Vec2d &transl, double /*rot*/) override
{ {
GLCanvas3D::WipeTowerInfo::apply_wipe_tower(m_orig_tr + transl, m_orig_rot); GLCanvas3D::WipeTowerInfo::apply_wipe_tower(m_orig_tr + transl, m_orig_rot, this->bed_index);
} }
void imbue_data(arr2::AnyWritable &datastore) const override void imbue_data(arr2::AnyWritable &datastore) const override
@ -132,12 +221,12 @@ struct WTH : public arr2::WipeTowerHandler
{ {
GLCanvas3D::WipeTowerInfo wti; GLCanvas3D::WipeTowerInfo wti;
ObjectID oid; ObjectID oid;
std::function<bool()> sel_pred; std::function<bool(int)> sel_pred;
BoundingBox xl_bb; BoundingBox xl_bb;
WTH(const ObjectID &objid, WTH(const ObjectID &objid,
const GLCanvas3D::WipeTowerInfo &w, const GLCanvas3D::WipeTowerInfo &w,
std::function<bool()> sel_predicate = [] { return false; }) std::function<bool(int)> sel_predicate = [](int){ return false; })
: wti(w), oid{objid}, sel_pred{std::move(sel_predicate)} : wti(w), oid{objid}, sel_pred{std::move(sel_predicate)}
{} {}
@ -158,39 +247,77 @@ struct WTH : public arr2::WipeTowerHandler
visit_(*this, fn); visit_(*this, fn);
} }
void set_selection_predicate(std::function<bool()> pred) override void set_selection_predicate(std::function<bool(int)> pred) override
{ {
sel_pred = std::move(pred); sel_pred = std::move(pred);
} }
ObjectID get_id() const override {
return this->oid;
}
}; };
arr2::SceneBuilder build_scene(Plater &plater, ArrangeSelectionMode mode) arr2::SceneBuilder build_scene(Plater &plater, ArrangeSelectionMode mode)
{ {
arr2::SceneBuilder builder; arr2::SceneBuilder builder;
const int current_bed{s_multiple_beds.get_active_bed()};
if (mode == ArrangeSelectionMode::SelectionOnly) { if (mode == ArrangeSelectionMode::SelectionOnly) {
auto sel = std::make_unique<GUISelectionMask>(&plater.get_selection()); auto sel = std::make_unique<GUISelectionMask>(&plater.get_selection());
builder.set_selection(std::move(sel)); builder.set_selection(std::move(sel));
} else if (mode == ArrangeSelectionMode::CurrentBedSelectionOnly) {
arr2::BedConstraints constraints;
for (const ModelObject *object : plater.model().objects) {
for (const ModelInstance *instance : object->instances) {
constraints.insert({instance->id(), current_bed});
}
}
builder.set_bed_constraints(std::move(constraints));
auto gui_selection{std::make_unique<GUISelectionMask>(&plater.get_selection())};
builder.set_selection(std::move(gui_selection));
} else if (mode == ArrangeSelectionMode::CurrentBedFull) {
std::set<ObjectID> instances_on_bed;
arr2::BedConstraints constraints;
for (const auto &instance_bed : s_multiple_beds.get_inst_map()) {
if (instance_bed.second == current_bed) {
instances_on_bed.emplace(instance_bed.first);
constraints.emplace(instance_bed);
}
}
builder.set_bed_constraints(std::move(constraints));
auto bed_selection{std::make_unique<BedSelectionMask>(
current_bed,
plater.model().objects,
instances_on_bed
)};
builder.set_selection(std::move(bed_selection));
} }
builder.set_arrange_settings(plater.canvas3D()->get_arrange_settings_view()); builder.set_arrange_settings(plater.canvas3D()->get_arrange_settings_view());
auto wti = plater.canvas3D()->get_wipe_tower_info(); const auto wipe_tower_infos = plater.canvas3D()->get_wipe_tower_infos();
AnyPtr<WTH> wth; std::vector<AnyPtr<arr2::WipeTowerHandler>> handlers;
if (wti) { for (const auto &info : wipe_tower_infos) {
wth = std::make_unique<WTH>(wipe_tower_instance_id(0), wti); if (info) {
} auto handler{std::make_unique<WTH>(wipe_tower_instance_id(info.bed_index()), info)};
if (plater.config() && is_XL_printer(*plater.config())) {
if (plater.config()) { handler->xl_bb = bounding_box(get_bed_shape(*plater.config()));
builder.set_bed(*plater.config()); }
if (wth && is_XL_printer(*plater.config())) { handlers.push_back(std::move(handler));
wth->xl_bb = bounding_box(get_bed_shape(*plater.config()));
} }
} }
builder.set_wipe_tower_handler(std::move(wth)); if (plater.config()) {
const BedsGrid::Gap gap{s_multiple_beds.get_bed_gap()};
builder.set_bed(*plater.config(), gap);
}
builder.set_wipe_tower_handlers(std::move(handlers));
builder.set_model(plater.model()); builder.set_model(plater.model());
if (plater.printer_technology() == ptSLA) if (plater.printer_technology() == ptSLA)

View File

@ -27,7 +27,7 @@ namespace GUI {
class Plater; class Plater;
enum class ArrangeSelectionMode { SelectionOnly, Full }; enum class ArrangeSelectionMode { SelectionOnly, Full, CurrentBedFull, CurrentBedSelectionOnly };
arr2::SceneBuilder build_scene( arr2::SceneBuilder build_scene(
Plater &plater, ArrangeSelectionMode mode = ArrangeSelectionMode::Full); Plater &plater, ArrangeSelectionMode mode = ArrangeSelectionMode::Full);

View File

@ -705,6 +705,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
view3D_canvas->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this); view3D_canvas->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this);
view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); }); view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); });
view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); }); view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); });
view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE_CURRENT_BED, [this](SimpleEvent&) { this->q->arrange_current_bed(); });
view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); }); view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); });
view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event<int>& evt) view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event<int>& evt)
@ -736,6 +737,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [this](SimpleEvent&) { delete_all_objects_from_model(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [this](SimpleEvent&) { delete_all_objects_from_model(); });
// view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); }); // view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE_CURRENT_BED, [this](SimpleEvent&) { this->q->arrange_current_bed(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); });
@ -1091,6 +1093,7 @@ void Plater::priv::update(unsigned int flags)
// Update the SLAPrint from the current Model, so that the reload_scene() // Update the SLAPrint from the current Model, so that the reload_scene()
// pulls the correct data. // pulls the correct data.
update_status = this->update_background_process(false, flags & (unsigned int)UpdateParams::POSTPONE_VALIDATION_ERROR_MESSAGE); update_status = this->update_background_process(false, flags & (unsigned int)UpdateParams::POSTPONE_VALIDATION_ERROR_MESSAGE);
s_multiple_beds.update_shown_beds(model, q->build_volume());
this->view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH); this->view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH);
this->preview->reload_print(); this->preview->reload_print();
if (force_background_processing_restart) if (force_background_processing_restart)
@ -5410,7 +5413,7 @@ void Plater::fill_bed_with_instances()
}; };
auto scene = arr2::Scene{ auto scene = arr2::Scene{
build_scene(*this, ArrangeSelectionMode::SelectionOnly)}; build_scene(*this, ArrangeSelectionMode::CurrentBedSelectionOnly)};
cbs.on_finished = [this](arr2::FillBedTaskResult &result) { cbs.on_finished = [this](arr2::FillBedTaskResult &result) {
auto [prototype_mi, pos] = arr2::find_instance_by_id(model(), result.prototype_id); auto [prototype_mi, pos] = arr2::find_instance_by_id(model(), result.prototype_id);
@ -5520,11 +5523,14 @@ void Plater::apply_cut_object_to_model(size_t obj_idx, const ModelObjectPtrs& ne
Selection& selection = p->get_selection(); Selection& selection = p->get_selection();
size_t last_id = p->model.objects.size() - 1; size_t last_id = p->model.objects.size() - 1;
for (size_t i = 0; i < new_objects.size(); ++i) for (size_t i = 0; i < new_objects.size(); ++i) {
selection.add_object((unsigned int)(last_id - i), i == 0); selection.add_object((unsigned int)(last_id - i), i == 0);
const ObjectID instance_id{p->model.objects[last_id - i]->instances.front()->id().id};
s_multiple_beds.set_instance_bed(instance_id, s_multiple_beds.get_active_bed());
}
UIThreadWorker w; UIThreadWorker w;
arrange(w, true); arrange(w, ArrangeSelectionMode::CurrentBedSelectionOnly);
w.wait_for_idle(); w.wait_for_idle();
} }
@ -6737,18 +6743,33 @@ static std::string concat_strings(const std::set<std::string> &strings,
void Plater::arrange() void Plater::arrange()
{ {
const auto mode{
wxGetKeyState(WXK_SHIFT) ?
ArrangeSelectionMode::SelectionOnly :
ArrangeSelectionMode::Full
};
if (p->can_arrange()) { if (p->can_arrange()) {
auto &w = get_ui_job_worker(); auto &w = get_ui_job_worker();
arrange(w, wxGetKeyState(WXK_SHIFT)); arrange(w, mode);
} }
} }
void Plater::arrange(Worker &w, bool selected) void Plater::arrange_current_bed()
{ {
ArrangeSelectionMode mode = selected ? const auto mode{
ArrangeSelectionMode::SelectionOnly : wxGetKeyState(WXK_SHIFT) ?
ArrangeSelectionMode::Full; ArrangeSelectionMode::CurrentBedSelectionOnly :
ArrangeSelectionMode::CurrentBedFull
};
if (p->can_arrange()) {
auto &w = get_ui_job_worker();
arrange(w, mode);
}
}
void Plater::arrange(Worker &w, const ArrangeSelectionMode &mode)
{
arr2::Scene arrscene{build_scene(*this, mode)}; arr2::Scene arrscene{build_scene(*this, mode)};
ArrangeJob2::Callbacks cbs; ArrangeJob2::Callbacks cbs;
@ -6782,10 +6803,7 @@ void Plater::arrange(Worker &w, bool selected)
concat_strings(names, "\n"))); concat_strings(names, "\n")));
} }
s_multiple_beds.update_after_load_or_arrange(model(), build_volume(), [this]() { canvas3D()->check_volumes_outside_state();
canvas3D()->check_volumes_outside_state();
s_multiple_beds.ensure_wipe_towers_on_beds(model(), get_fff_prints());
});
update(static_cast<unsigned int>(UpdateParams::FORCE_FULL_SCREEN_REFRESH)); update(static_cast<unsigned int>(UpdateParams::FORCE_FULL_SCREEN_REFRESH));
wxGetApp().obj_manipul()->set_dirty(); wxGetApp().obj_manipul()->set_dirty();

View File

@ -64,6 +64,7 @@ struct Camera;
class GLToolbar; class GLToolbar;
class UserAccount; class UserAccount;
class PresetArchiveDatabase; class PresetArchiveDatabase;
enum class ArrangeSelectionMode;
class Plater: public wxPanel class Plater: public wxPanel
{ {
@ -285,9 +286,10 @@ public:
GLCanvas3D* get_current_canvas3D(); GLCanvas3D* get_current_canvas3D();
void render_sliders(GLCanvas3D& canvas); void render_sliders(GLCanvas3D& canvas);
void arrange(); void arrange();
void arrange(Worker &w, bool selected); void arrange_current_bed();
void arrange(Worker &w, const ArrangeSelectionMode &selected);
void set_current_canvas_as_dirty(); void set_current_canvas_as_dirty();
void unbind_canvas_event_handlers(); void unbind_canvas_event_handlers();

View File

@ -117,7 +117,7 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came
const Selection& selection = wxGetApp().plater()->get_selection(); const Selection& selection = wxGetApp().plater()->get_selection();
if (selection.is_single_volume() || selection.is_single_modifier()) { if (selection.is_single_volume() || selection.is_single_modifier()) {
const GLVolume* volume = selection.get_first_volume(); const GLVolume* volume = selection.get_first_volume();
if (!volume->is_wipe_tower && !volume->is_sla_pad() && !volume->is_sla_support()) if (!volume->is_wipe_tower() && !volume->is_sla_pad() && !volume->is_sla_support())
m_selected_volume_id = *selection.get_volume_idxs().begin(); m_selected_volume_id = *selection.get_volume_idxs().begin();
} }
} }

View File

@ -148,7 +148,7 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec
return; return;
// wipe tower is already selected // wipe tower is already selected
if (is_wipe_tower() && volume->is_wipe_tower && contains_volume(volume_idx)) if (is_wipe_tower() && volume->is_wipe_tower() && contains_volume(volume_idx))
return; return;
bool keep_instance_mode = (m_mode == Instance) && !as_single_selection; bool keep_instance_mode = (m_mode == Instance) && !as_single_selection;
@ -156,8 +156,8 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec
// resets the current list if needed // resets the current list if needed
bool needs_reset = as_single_selection && !already_contained; bool needs_reset = as_single_selection && !already_contained;
needs_reset |= volume->is_wipe_tower; needs_reset |= volume->is_wipe_tower();
needs_reset |= is_wipe_tower() && !volume->is_wipe_tower; needs_reset |= is_wipe_tower() && !volume->is_wipe_tower();
needs_reset |= as_single_selection && !is_any_modifier() && volume->is_modifier; needs_reset |= as_single_selection && !is_any_modifier() && volume->is_modifier;
needs_reset |= is_any_modifier() && !volume->is_modifier; needs_reset |= is_any_modifier() && !volume->is_modifier;
@ -381,7 +381,7 @@ void Selection::add_all()
unsigned int count = 0; unsigned int count = 0;
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) {
if (!(*m_volumes)[i]->is_wipe_tower) if (!(*m_volumes)[i]->is_wipe_tower())
++count; ++count;
} }
@ -394,7 +394,7 @@ void Selection::add_all()
clear(); clear();
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) {
if (!(*m_volumes)[i]->is_wipe_tower) if (!(*m_volumes)[i]->is_wipe_tower())
do_add_volume(i); do_add_volume(i);
} }
@ -1445,7 +1445,7 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co
if (done.size() == m_volumes->size()) if (done.size() == m_volumes->size())
break; break;
if ((*m_volumes)[i]->is_wipe_tower) if ((*m_volumes)[i]->is_wipe_tower())
continue; continue;
const int object_idx = (*m_volumes)[i]->object_idx(); const int object_idx = (*m_volumes)[i]->object_idx();
@ -1919,7 +1919,7 @@ void Selection::update_type()
m_type = Empty; m_type = Empty;
else if (m_list.size() == 1) { else if (m_list.size() == 1) {
const GLVolume* first = (*m_volumes)[*m_list.begin()]; const GLVolume* first = (*m_volumes)[*m_list.begin()];
if (first->is_wipe_tower) if (first->is_wipe_tower())
m_type = WipeTower; m_type = WipeTower;
else if (first->is_modifier) { else if (first->is_modifier) {
m_type = SingleModifier; m_type = SingleModifier;
@ -2740,7 +2740,7 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_
if (done.size() == m_volumes->size()) if (done.size() == m_volumes->size())
break; break;
const GLVolume* volume_i = (*m_volumes)[i]; const GLVolume* volume_i = (*m_volumes)[i];
if (volume_i->is_wipe_tower) if (volume_i->is_wipe_tower())
continue; continue;
const int object_idx = volume_i->object_idx(); const int object_idx = volume_i->object_idx();
@ -2785,7 +2785,7 @@ void Selection::synchronize_unselected_volumes()
{ {
for (unsigned int i : m_list) { for (unsigned int i : m_list) {
const GLVolume* volume = (*m_volumes)[i]; const GLVolume* volume = (*m_volumes)[i];
if (volume->is_wipe_tower) if (volume->is_wipe_tower())
continue; continue;
const int object_idx = volume->object_idx(); const int object_idx = volume->object_idx();
@ -2813,7 +2813,7 @@ void Selection::ensure_on_bed()
for (size_t i = 0; i < m_volumes->size(); ++i) { for (size_t i = 0; i < m_volumes->size(); ++i) {
GLVolume* volume = (*m_volumes)[i]; GLVolume* volume = (*m_volumes)[i];
if (!volume->is_wipe_tower && !volume->is_modifier && if (!volume->is_wipe_tower() && !volume->is_modifier &&
std::find(m_cache.sinking_volumes.begin(), m_cache.sinking_volumes.end(), i) == m_cache.sinking_volumes.end()) { std::find(m_cache.sinking_volumes.begin(), m_cache.sinking_volumes.end(), i) == m_cache.sinking_volumes.end()) {
const double min_z = volume->transformed_convex_hull_bounding_box().min.z(); const double min_z = volume->transformed_convex_hull_bounding_box().min.z();
std::pair<int, int> instance = std::make_pair(volume->object_idx(), volume->instance_idx()); std::pair<int, int> instance = std::make_pair(volume->object_idx(), volume->instance_idx());
@ -2840,7 +2840,7 @@ void Selection::ensure_not_below_bed()
for (size_t i = 0; i < m_volumes->size(); ++i) { for (size_t i = 0; i < m_volumes->size(); ++i) {
GLVolume* volume = (*m_volumes)[i]; GLVolume* volume = (*m_volumes)[i];
if (!volume->is_wipe_tower && !volume->is_modifier) { if (!volume->is_wipe_tower() && !volume->is_modifier) {
const double max_z = volume->transformed_convex_hull_bounding_box().max.z(); const double max_z = volume->transformed_convex_hull_bounding_box().max.z();
const std::pair<int, int> instance = std::make_pair(volume->object_idx(), volume->instance_idx()); const std::pair<int, int> instance = std::make_pair(volume->object_idx(), volume->instance_idx());
InstancesToZMap::iterator it = instances_max_z.find(instance); InstancesToZMap::iterator it = instances_max_z.find(instance);

View File

@ -386,7 +386,7 @@ template<> inline Slic3r::arr2::RectangleBed init_bed<Slic3r::arr2::RectangleBed
template<> inline Slic3r::arr2::CircleBed init_bed<Slic3r::arr2::CircleBed>() template<> inline Slic3r::arr2::CircleBed init_bed<Slic3r::arr2::CircleBed>()
{ {
return Slic3r::arr2::CircleBed{Slic3r::Point::Zero(), scaled(300.)}; return Slic3r::arr2::CircleBed{Slic3r::Point::Zero(), scaled(300.), Slic3r::BedsGrid::Gap{0, 0}};
} }
template<> inline Slic3r::arr2::IrregularBed init_bed<Slic3r::arr2::IrregularBed>() template<> inline Slic3r::arr2::IrregularBed init_bed<Slic3r::arr2::IrregularBed>()
@ -640,6 +640,7 @@ struct RectangleItem {
void set_bed_index(int idx) { bed_index = idx; } void set_bed_index(int idx) { bed_index = idx; }
int get_bed_index() const noexcept { return bed_index; } int get_bed_index() const noexcept { return bed_index; }
std::optional<int> get_bed_constraint() const { return std::nullopt; }
void set_translation(const Vec2crd &tr) { translation = tr; } void set_translation(const Vec2crd &tr) { translation = tr; }
const Vec2crd & get_translation() const noexcept { return translation; } const Vec2crd & get_translation() const noexcept { return translation; }

View File

@ -135,7 +135,7 @@ TEST_CASE("ModelInstance should be retrievable when imbued into ArrangeItem",
arr2::ArrangeItem itm; arr2::ArrangeItem itm;
arr2::PhysicalOnlyVBedHandler vbedh; arr2::PhysicalOnlyVBedHandler vbedh;
auto vbedh_ptr = static_cast<arr2::VirtualBedHandler *>(&vbedh); auto vbedh_ptr = static_cast<arr2::VirtualBedHandler *>(&vbedh);
auto arrbl = arr2::ArrangeableModelInstance{mi, vbedh_ptr, nullptr, {0, 0}}; auto arrbl = arr2::ArrangeableModelInstance{mi, vbedh_ptr, nullptr, {0, 0}, std::nullopt};
arr2::imbue_id(itm, arrbl.id()); arr2::imbue_id(itm, arrbl.id());
std::optional<ObjectID> id_returned = arr2::retrieve_id(itm); std::optional<ObjectID> id_returned = arr2::retrieve_id(itm);
@ -330,7 +330,7 @@ auto create_vbed_handler<Slic3r::arr2::YStriderVBedHandler>(const Slic3r::Boundi
template<> template<>
auto create_vbed_handler<Slic3r::arr2::GridStriderVBedHandler>(const Slic3r::BoundingBox &bedbb, coord_t gap) auto create_vbed_handler<Slic3r::arr2::GridStriderVBedHandler>(const Slic3r::BoundingBox &bedbb, coord_t gap)
{ {
return Slic3r::arr2::GridStriderVBedHandler{bedbb, gap}; return Slic3r::arr2::GridStriderVBedHandler{bedbb, {gap, gap}};
} }
TEMPLATE_TEST_CASE("Common virtual bed handlers", TEMPLATE_TEST_CASE("Common virtual bed handlers",
@ -628,7 +628,7 @@ TEMPLATE_TEST_CASE("Bed needs to be completely filled with 1cm cubes",
ModelObject* new_object = m.add_object(); ModelObject* new_object = m.add_object();
new_object->name = "10mm_box"; new_object->name = "10mm_box";
new_object->add_instance(); ModelInstance *instance = new_object->add_instance();
TriangleMesh mesh = make_cube(10., 10., 10.); TriangleMesh mesh = make_cube(10., 10., 10.);
ModelVolume* new_volume = new_object->add_volume(mesh); ModelVolume* new_volume = new_object->add_volume(mesh);
new_volume->name = new_object->name; new_volume->name = new_object->name;
@ -641,11 +641,15 @@ TEMPLATE_TEST_CASE("Bed needs to be completely filled with 1cm cubes",
arr2::FixedSelection sel({{true}}); arr2::FixedSelection sel({{true}});
arr2::BedConstraints constraints;
constraints.insert({instance->id(), 0});
arr2::Scene scene{arr2::SceneBuilder{} arr2::Scene scene{arr2::SceneBuilder{}
.set_model(m) .set_model(m)
.set_arrange_settings(settings) .set_arrange_settings(settings)
.set_selection(&sel) .set_selection(&sel)
.set_bed(cfg)}; .set_bed_constraints(std::move(constraints))
.set_bed(cfg, Point::new_scale(10, 10))};
auto task = arr2::FillBedTask<ArrItem>::create(scene); auto task = arr2::FillBedTask<ArrItem>::create(scene);
auto result = task->process_native(arr2::DummyCtl{}); auto result = task->process_native(arr2::DummyCtl{});
@ -654,7 +658,7 @@ TEMPLATE_TEST_CASE("Bed needs to be completely filled with 1cm cubes",
store_3mf("fillbed_10mm_result.3mf", &m, &cfg, false); store_3mf("fillbed_10mm_result.3mf", &m, &cfg, false);
Points bedpts = get_bed_shape(cfg); Points bedpts = get_bed_shape(cfg);
arr2::ArrangeBed bed = arr2::to_arrange_bed(bedpts); arr2::ArrangeBed bed = arr2::to_arrange_bed(bedpts, Point::new_scale(10, 10));
REQUIRE(bed.which() == 1); // Rectangle bed REQUIRE(bed.which() == 1); // Rectangle bed
@ -799,7 +803,7 @@ TEST_CASE("Testing arrangement involving virtual beds", "[arrange2][integration]
DynamicPrintConfig cfg; DynamicPrintConfig cfg;
cfg.load_from_ini(std::string(TEST_DATA_DIR PATH_SEPARATOR) + "default_fff.ini", cfg.load_from_ini(std::string(TEST_DATA_DIR PATH_SEPARATOR) + "default_fff.ini",
ForwardCompatibilitySubstitutionRule::Enable); ForwardCompatibilitySubstitutionRule::Enable);
auto bed = arr2::to_arrange_bed(get_bed_shape(cfg)); auto bed = arr2::to_arrange_bed(get_bed_shape(cfg), Point::new_scale(10, 10));
auto bedbb = bounding_box(bed); auto bedbb = bounding_box(bed);
auto bedsz = unscaled(bedbb.size()); auto bedsz = unscaled(bedbb.size());
@ -815,7 +819,7 @@ TEST_CASE("Testing arrangement involving virtual beds", "[arrange2][integration]
arr2::Scene scene{arr2::SceneBuilder{} arr2::Scene scene{arr2::SceneBuilder{}
.set_model(model) .set_model(model)
.set_arrange_settings(settings) .set_arrange_settings(settings)
.set_bed(cfg)}; .set_bed(cfg, Point::new_scale(10, 10))};
auto itm_conv = arr2::ArrangeableToItemConverter<arr2::ArrangeItem>::create(scene); auto itm_conv = arr2::ArrangeableToItemConverter<arr2::ArrangeItem>::create(scene);
@ -883,7 +887,7 @@ public:
}; };
class MocWTH : public WipeTowerHandler { class MocWTH : public WipeTowerHandler {
std::function<bool()> m_sel_pred; std::function<bool(int)> m_sel_pred;
ObjectID m_id; ObjectID m_id;
public: public:
@ -891,18 +895,22 @@ public:
void visit(std::function<void(Arrangeable &)> fn) override void visit(std::function<void(Arrangeable &)> fn) override
{ {
MocWT wt{m_id, Polygon{}, m_sel_pred}; MocWT wt{m_id, Polygon{}, 0, m_sel_pred};
fn(wt); fn(wt);
} }
void visit(std::function<void(const Arrangeable &)> fn) const override void visit(std::function<void(const Arrangeable &)> fn) const override
{ {
MocWT wt{m_id, Polygon{}, m_sel_pred}; MocWT wt{m_id, Polygon{}, 0, m_sel_pred};
fn(wt); fn(wt);
} }
void set_selection_predicate(std::function<bool()> pred) override void set_selection_predicate(std::function<bool(int)> pred) override
{ {
m_sel_pred = std::move(pred); m_sel_pred = std::move(pred);
} }
ObjectID get_id() const override {
return m_id;
}
}; };
}} // namespace Slic3r::arr2 }} // namespace Slic3r::arr2
@ -974,7 +982,7 @@ TEST_CASE("Test SceneBuilder", "[arrange2][integration]")
WHEN("a scene is built with a bed initialized from this DynamicPrintConfig") WHEN("a scene is built with a bed initialized from this DynamicPrintConfig")
{ {
arr2::Scene scene(arr2::SceneBuilder{}.set_bed(cfg)); arr2::Scene scene(arr2::SceneBuilder{}.set_bed(cfg, Point::new_scale(10, 10)));
auto bedbb = bounding_box(get_bed_shape(cfg)); auto bedbb = bounding_box(get_bed_shape(cfg));
@ -1001,7 +1009,10 @@ TEST_CASE("Test SceneBuilder", "[arrange2][integration]")
arr2::SceneBuilder bld; arr2::SceneBuilder bld;
Model mdl; Model mdl;
bld.set_model(mdl); bld.set_model(mdl);
bld.set_wipe_tower_handler(std::make_unique<arr2::MocWTH>(wipe_tower_instance_id(0)));
std::vector<AnyPtr<arr2::WipeTowerHandler>> handlers;
handlers.push_back(std::make_unique<arr2::MocWTH>(wipe_tower_instance_id(0)));
bld.set_wipe_tower_handlers(std::move(handlers));
WHEN("the selection mask is initialized as a fallback default in the created scene") WHEN("the selection mask is initialized as a fallback default in the created scene")
{ {

View File

@ -254,7 +254,7 @@ void init_print(std::vector<TriangleMesh> &&meshes, Slic3r::Print &print, Slic3r
double distance = min_object_distance(config); double distance = min_object_distance(config);
arr2::ArrangeSettings arrange_settings{}; arr2::ArrangeSettings arrange_settings{};
arrange_settings.set_distance_from_objects(distance); arrange_settings.set_distance_from_objects(distance);
arr2::ArrangeBed bed{arr2::to_arrange_bed(get_bed_shape(config))}; arr2::ArrangeBed bed{arr2::to_arrange_bed(get_bed_shape(config), BedsGrid::Gap{0, 0})};
if (duplicate_count > 1) { if (duplicate_count > 1) {
duplicate(model, duplicate_count, bed, arrange_settings); duplicate(model, duplicate_count, bed, arrange_settings);
} }

View File

@ -43,7 +43,7 @@ SCENARIO("Model construction", "[Model]") {
} }
model_object->add_instance(); model_object->add_instance();
arrange_objects(model, arrange_objects(model,
arr2::to_arrange_bed(get_bed_shape(config)), arr2::to_arrange_bed(get_bed_shape(config), Point::new_scale(10, 10)),
arr2::ArrangeSettings{}.set_distance_from_objects( arr2::ArrangeSettings{}.set_distance_from_objects(
min_object_distance(config))); min_object_distance(config)));

View File

@ -26,6 +26,7 @@ add_executable(${_TEST_NAME}_tests
test_stl.cpp test_stl.cpp
test_meshboolean.cpp test_meshboolean.cpp
test_marchingsquares.cpp test_marchingsquares.cpp
test_multiple_beds.cpp
test_region_expansion.cpp test_region_expansion.cpp
test_timeutils.cpp test_timeutils.cpp
test_utils.cpp test_utils.cpp

View File

@ -0,0 +1,35 @@
#include <catch2/catch.hpp>
#include <libslic3r/MultipleBeds.hpp>
#include <numeric>
using namespace Slic3r;
TEST_CASE("Conversion between grid coords and index", "[MultipleBeds]")
{
std::vector<BedsGrid::Index> original_indices(10);
std::iota(original_indices.begin(), original_indices.end(), 0);
// Add indexes covering the whole int positive range.
const int n{100};
std::generate_n(std::back_inserter(original_indices), n, [i = 1]() mutable {
return std::numeric_limits<int>::max() / n * i++;
});
std::vector<BedsGrid::GridCoords> coords;
std::transform(
original_indices.begin(),
original_indices.end(),
std::back_inserter(coords),
BedsGrid::index2grid_coords
);
std::vector<BedsGrid::Index> indices;
std::transform(
coords.begin(),
coords.end(),
std::back_inserter(indices),
BedsGrid::grid_coords2index
);
CHECK(original_indices == indices);
}

View File

@ -90,14 +90,14 @@ TEST_CASE("Basic arrange with cube", "[arrangejob]") {
arr2::ArrangeSettings settings; arr2::ArrangeSettings settings;
Points bedpts = get_bed_shape(cfg); Points bedpts = get_bed_shape(cfg);
arr2::ArrangeBed bed = arr2::to_arrange_bed(bedpts); arr2::ArrangeBed bed = arr2::to_arrange_bed(bedpts, BedsGrid::Gap{0, 0});
SECTION("Single cube needs to be centered") { SECTION("Single cube needs to be centered") {
w.push(std::make_unique<ArrangeJob2>(arr2::Scene{ w.push(std::make_unique<ArrangeJob2>(arr2::Scene{
arr2::SceneBuilder{} arr2::SceneBuilder{}
.set_model(m) .set_model(m)
.set_arrange_settings(&settings) .set_arrange_settings(&settings)
.set_bed(cfg)})); .set_bed(cfg, BedsGrid::Gap{0, 0})}));
w.process_events(); w.process_events();
@ -126,7 +126,7 @@ TEST_CASE("Basic arrange with cube", "[arrangejob]") {
arr2::Scene scene{arr2::SceneBuilder{} arr2::Scene scene{arr2::SceneBuilder{}
.set_model(m) .set_model(m)
.set_arrange_settings(&settings) .set_arrange_settings(&settings)
.set_bed(cfg) .set_bed(cfg, BedsGrid::Gap{0, 0})
.set_selection(&sel)}; .set_selection(&sel)};
w.push(std::make_unique<ArrangeJob2>(std::move(scene))); w.push(std::make_unique<ArrangeJob2>(std::move(scene)));
@ -160,7 +160,7 @@ TEST_CASE("Basic arrange with cube", "[arrangejob]") {
arr2::Scene scene{arr2::SceneBuilder{} arr2::Scene scene{arr2::SceneBuilder{}
.set_model(m) .set_model(m)
.set_arrange_settings(&settings) .set_arrange_settings(&settings)
.set_bed(cfg) .set_bed(cfg, BedsGrid::Gap{0, 0})
.set_selection(&sel)}; .set_selection(&sel)};
w.push(std::make_unique<ArrangeJob2>(std::move(scene))); w.push(std::make_unique<ArrangeJob2>(std::move(scene)));
@ -217,7 +217,7 @@ TEST_CASE("Basic arrange with cube", "[arrangejob]") {
arr2::Scene scene{arr2::SceneBuilder{} arr2::Scene scene{arr2::SceneBuilder{}
.set_model(m) .set_model(m)
.set_arrange_settings(&settings) .set_arrange_settings(&settings)
.set_bed(cfg)}; .set_bed(cfg, Point::new_scale(10, 10))};
w.push(std::make_unique<ArrangeJob2>(std::move(scene))); w.push(std::make_unique<ArrangeJob2>(std::move(scene)));
w.process_events(); w.process_events();
@ -266,7 +266,7 @@ TEST_CASE("Test for modifying model during arrangement", "[arrangejob][fillbedjo
new_volume->name = new_object->name; new_volume->name = new_object->name;
Points bedpts = get_bed_shape(cfg); Points bedpts = get_bed_shape(cfg);
arr2::ArrangeBed bed = arr2::to_arrange_bed(bedpts); arr2::ArrangeBed bed = arr2::to_arrange_bed(bedpts, BedsGrid::Gap{0, 0});
BoostThreadWorker w(std::make_unique<DummyProgress>()); BoostThreadWorker w(std::make_unique<DummyProgress>());
RandomArrangeSettings settings; RandomArrangeSettings settings;
@ -278,7 +278,7 @@ TEST_CASE("Test for modifying model during arrangement", "[arrangejob][fillbedjo
arr2::Scene scene{arr2::SceneBuilder{} arr2::Scene scene{arr2::SceneBuilder{}
.set_model(m) .set_model(m)
.set_arrange_settings(&settings) .set_arrange_settings(&settings)
.set_bed(cfg)}; .set_bed(cfg, BedsGrid::Gap{0, 0})};
ArrangeJob2::Callbacks cbs; ArrangeJob2::Callbacks cbs;
cbs.on_prepared = [&m] (auto &) { cbs.on_prepared = [&m] (auto &) {