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) -->
<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">
<g id="arrange">
<g>
<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"/>
</g>
<g>
<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"/>
</g>
<g>
<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"/>
</g>
<g>
<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"/>
</g>
<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"
sodipodi:docname="arrange_current.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs943" /><sodipodi:namedview
id="namedview941"
pagecolor="#ffffff"
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>
</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.
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;
arrange_cfg.set_distance_from_objects(min_object_distance(m_print_config));

View File

@ -46,7 +46,7 @@ void arrange(SelectionStrategy &&selstrategy,
// Dispatch:
arrange(std::forward<SelectionStrategy>(selstrategy),
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::map<int, BoundingBox> pilebb;
@ -399,6 +399,7 @@ ArrItem ConvexItemConverter<ArrItem>::convert(const Arrangeable &arrbl,
set_bed_index(ret, bed_index);
set_priority(ret, arrbl.priority());
set_bed_constraint(ret, arrbl.bed_constraint());
imbue_id(ret, arrbl.id());
if constexpr (IsWritableDataStore<ArrItem>)
@ -416,6 +417,7 @@ ArrItem AdvancedItemConverter<ArrItem>::convert(const Arrangeable &arrbl,
set_bed_index(ret, bed_index);
set_priority(ret, arrbl.priority());
set_bed_constraint(ret, arrbl.bed_constraint());
imbue_id(ret, arrbl.id());
if constexpr (IsWritableDataStore<ArrItem>)
arrbl.imbue_data(AnyWritableDataStore{ret});

View File

@ -139,6 +139,10 @@ void arrange(
int bedidx = 0;
while (!was_packed && !is_cancelled()) {
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);
auto remaining = Range{std::next(static_cast<SConstIt>(it)),
@ -157,6 +161,10 @@ void arrange(
sel.on_arranged_fn(*it, bed, packed_range, remaining);
} else {
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 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(); }
// 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_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>>;
@ -69,6 +79,11 @@ template<class T> int get_priority(const T &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:
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);
}
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
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);
}
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;
double avg_dist = 0;
@ -96,7 +96,7 @@ static CircleBed to_circle(const Point &center, const Points &points)
avg_dist /= vertex_distances.size();
CircleBed ret(center, avg_dist);
CircleBed ret(center, avg_dist, gap);
for (auto el : vertex_distances) {
if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) {
ret = {};
@ -107,7 +107,7 @@ static CircleBed to_circle(const Point &center, const Points &points)
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())
return fn(InfiniteBed{});
@ -115,23 +115,23 @@ template<class Fn> auto call_with_bed(const Points &bed, Fn &&fn)
return fn(InfiniteBed{bed.front()});
else {
auto bb = BoundingBox(bed);
CircleBed circ = to_circle(bb.center(), bed);
CircleBed circ = to_circle(bb.center(), bed, gap);
auto parea = poly_area(bed);
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)
return fn(circ);
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;
call_with_bed(bedpts, [&](const auto &bed) { ret = bed; });
call_with_bed(bedpts, gap, [&](const auto &bed) { ret = bed; });
return ret;
}

View File

@ -16,6 +16,7 @@
#include <limits>
#include <type_traits>
#include "libslic3r/MultipleBeds.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/libslic3r.h"
@ -35,13 +36,18 @@ struct InfiniteBed {
BoundingBox bounding_box(const InfiniteBed &bed);
inline InfiniteBed offset(const InfiniteBed &bed, coord_t) { return bed; }
inline BedsGrid::Gap bed_gap(const InfiniteBed &)
{
return BedsGrid::Gap::Zero();
}
struct RectangleBed {
BoundingBox bb;
BedsGrid::Gap gap;
explicit RectangleBed(const BoundingBox &bedbb) : bb{bedbb} {}
explicit RectangleBed(coord_t w, coord_t h, Point c = {0, 0}):
bb{{c.x() - w / 2, c.y() - h / 2}, {c.x() + w / 2, c.y() + h / 2}}
explicit RectangleBed(const BoundingBox &bedbb, const BedsGrid::Gap &gap) : bb{bedbb}, gap{gap} {}
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}}, gap{gap}
{}
coord_t width() const { return bb.size().x(); }
@ -54,6 +60,9 @@ inline RectangleBed offset(RectangleBed bed, coord_t v)
bed.bb.offset(v);
return bed;
}
inline BedsGrid::Gap bed_gap(const RectangleBed &bed) {
return bed.gap;
}
Polygon to_rectangle(const BoundingBox &bb);
@ -65,16 +74,19 @@ inline Polygon to_rectangle(const RectangleBed &bed)
class CircleBed {
Point m_center;
double m_radius;
BedsGrid::Gap m_gap;
public:
CircleBed(): m_center(0, 0), m_radius(NaNd) {}
explicit CircleBed(const Point& c, double r)
CircleBed(): m_center(0, 0), m_radius(NaNd), m_gap(BedsGrid::Gap::Zero()) {}
explicit CircleBed(const Point& c, double r, const BedsGrid::Gap &g)
: m_center(c)
, m_radius(r)
, m_gap(g)
{}
double radius() const { return m_radius; }
const Point& center() const { return m_center; }
const BedsGrid::Gap &gap() const { return m_gap; }
};
// 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)
{
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)
{
return get_extents(bed.poly);
@ -103,6 +119,10 @@ inline IrregularBed offset(IrregularBed bed, coord_t v)
bed.poly = offset_ex(bed.poly, v);
return bed;
}
inline BedsGrid::Gap bed_gap(const IrregularBed &bed)
{
return bed.gap;
}
using ArrangeBed =
boost::variant<InfiniteBed, RectangleBed, CircleBed, IrregularBed>;
@ -124,6 +144,15 @@ inline ArrangeBed offset(ArrangeBed bed, coord_t v)
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)
{
auto bbsz = bb.size();
@ -187,7 +216,7 @@ inline ExPolygons to_expolygons(const ArrangeBed &bed)
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<> struct IsRectangular_<RectangleBed>: public std::true_type {};

View File

@ -74,7 +74,7 @@ struct RectangleOverfitKernelWrapper {
for (auto &fitm : all_items_range(packing_context))
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,
remaining_items);
}

View File

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

View File

@ -159,6 +159,7 @@ ArrangeItem &ArrangeItem::operator=(const ArrangeItem &other)
m_datastore = other.m_datastore;
m_bed_idx = other.m_bed_idx;
m_priority = other.m_priority;
m_bed_constraint = other.m_bed_constraint;
if (other.m_envelope.get() == &other.m_shape)
m_envelope = &m_shape;
@ -190,6 +191,7 @@ ArrangeItem &ArrangeItem::operator=(ArrangeItem &&other) noexcept
m_datastore = std::move(other.m_datastore);
m_bed_idx = other.m_bed_idx;
m_priority = other.m_priority;
m_bed_constraint = other.m_bed_constraint;
if (other.m_envelope.get() == &other.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_priority{0}; // For sorting
std::optional<int> m_bed_constraint;
public:
ArrangeItem() = default;
@ -180,9 +181,11 @@ public:
int bed_idx() const { return m_bed_idx; }
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 priority(int v) { m_priority = v; }
void bed_constraint(std::optional<int> v) { m_bed_constraint = v; }
const ArbitraryDataStore &datastore() const { return m_datastore; }
ArbitraryDataStore &datastore() { return m_datastore; }
@ -239,6 +242,11 @@ template<> struct ArrangeItemTraits_<ArrangeItem>
return itm.priority();
}
static std::optional<int> get_bed_constraint(const ArrangeItem &itm)
{
return itm.bed_constraint();
}
// Setters:
static void set_translation(ArrangeItem &itm, const Vec2crd &v)
@ -255,6 +263,11 @@ template<> struct ArrangeItemTraits_<ArrangeItem>
{
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.

View File

@ -37,6 +37,7 @@ class SimpleArrangeItem {
double m_rotation = 0.;
int m_priority = 0;
int m_bed_idx = Unarranged;
std::optional<int> m_bed_constraint;
std::vector<double> m_allowed_rotations = {0.};
ObjectID m_obj_id;
@ -50,11 +51,15 @@ public:
double get_rotation() const noexcept { return m_rotation; }
int get_priority() const noexcept { return m_priority; }
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_rotation(double v) noexcept { m_rotation = v; }
void set_priority(int v) noexcept { m_priority = 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; }
Polygon outline() const;

View File

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

View File

@ -99,6 +99,8 @@ public:
// objects are arranged first.
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
// core by overriding this method. This implies that the specific Arranger
// 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;
}
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;
// SceneBuilderBase is intended for Scene construction. A simple constructor
@ -238,9 +248,9 @@ public:
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));
}

View File

@ -14,6 +14,7 @@
#include <iterator>
#include "libslic3r/Model.hpp"
#include "libslic3r/MultipleBeds.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/SLAPrint.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 (is_infinite_bed(m_bed)) {
set_bed(*m_fff_print);
set_bed(*m_fff_print, BedsGrid::Gap::Zero());
} else {
set_brim_and_skirt();
}
@ -211,28 +212,28 @@ void SceneBuilder::build_arrangeable_slicer_model(ArrangeableSlicerModel &amodel
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)
m_xl_printer = is_XL_printer(m_fff_print->config());
bool has_wipe_tower = false;
m_wipetower_handler->visit(
[&has_wipe_tower](const Arrangeable &arrbl) { has_wipe_tower = true; });
const bool has_wipe_tower = !m_wipetower_handlers.empty();
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_model = std::move(m_model);
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(
[&amodel] { return amodel.m_selmask->is_wipe_tower(); });
for (auto &wth : amodel.m_wths) {
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
@ -331,40 +332,19 @@ Transform3d YStriderVBedHandler::get_physical_bed_trafo(int bed_index) const
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
{
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)
{
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 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
{
Vec2i crd = raw2grid(bed_idx);
Vec2i crd = BedsGrid::index2grid_coords(bed_idx);
Transform3d ret = m_xstrider.get_physical_bed_trafo(crd.x()) *
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);
}
SceneBuilder &&SceneBuilder::set_bed(const DynamicPrintConfig &cfg)
SceneBuilder &&SceneBuilder::set_bed(const DynamicPrintConfig &cfg, const BedsGrid::Gap &gap)
{
Points bedpts = get_bed_shape(cfg);
@ -473,19 +453,19 @@ SceneBuilder &&SceneBuilder::set_bed(const DynamicPrintConfig &cfg)
m_xl_printer = true;
}
m_bed = arr2::to_arrange_bed(bedpts);
m_bed = arr2::to_arrange_bed(bedpts, gap);
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());
if (is_XL_printer(print.config())) {
m_bed = XLBed{get_extents(bedpts)};
m_bed = XLBed{get_extents(bedpts), gap};
} else {
m_bed = arr2::to_arrange_bed(bedpts);
m_bed = arr2::to_arrange_bed(bedpts, gap);
}
set_brim_and_skirt();
@ -499,11 +479,13 @@ SceneBuilder &&SceneBuilder::set_sla_print(const SLAPrint *slaprint)
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)
{
return bed_idx == PhysicalBedId;
return bed_idx == this->bed_index;
}
bool PhysicalOnlyVBedHandler::assign_bed(VBedPlaceable &inst, int bed_idx)
@ -523,7 +505,9 @@ void ArrangeableSlicerModel::for_each_arrangeable(
{
for_each_arrangeable_(*this, fn);
m_wth->visit(fn);
for (auto &wth : m_wths) {
wth->visit(fn);
}
}
void ArrangeableSlicerModel::for_each_arrangeable(
@ -531,7 +515,9 @@ void ArrangeableSlicerModel::for_each_arrangeable(
{
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)
@ -549,13 +535,30 @@ ObjectID ArrangeableSlicerModel::add_arrangeable(const ObjectID &prototype_id)
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>
void ArrangeableSlicerModel::for_each_arrangeable_(Self &&self, Fn &&fn)
{
InstPos pos;
for (auto *obj : self.m_model->objects) {
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);
++pos.inst_idx;
}
@ -567,16 +570,23 @@ void ArrangeableSlicerModel::for_each_arrangeable_(Self &&self, Fn &&fn)
template<class Self, class Fn>
void ArrangeableSlicerModel::visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn)
{
if (id == wipe_tower_instance_id(0)) {
self.m_wth->visit(fn);
return;
for (auto &wth : self.m_wths) {
if (id == wth->get_id()) {
wth->visit(fn);
return;
}
}
auto [inst, pos] = find_instance_by_id(*self.m_model, id);
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);
}
}
@ -600,7 +610,7 @@ void ArrangeableSLAPrint::for_each_arrangeable_(Self &&self, Fn &&fn)
for (auto *obj : self.m_model->objects) {
for (auto *inst : obj->instances) {
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();
const SLAPrintObject *po =
@ -627,7 +637,9 @@ void ArrangeableSLAPrint::for_each_arrangeable(
{
for_each_arrangeable_(*this, fn);
m_wth->visit(fn);
for (auto &wth : m_wths) {
wth->visit(fn);
}
}
void ArrangeableSLAPrint::for_each_arrangeable(
@ -635,7 +647,9 @@ void ArrangeableSLAPrint::for_each_arrangeable(
{
for_each_arrangeable_(*this, fn);
m_wth->visit(fn);
for (auto &wth : m_wths) {
wth->visit(fn);
}
}
template<class Self, class Fn>
@ -645,7 +659,7 @@ void ArrangeableSLAPrint::visit_arrangeable_(Self &&self, const ObjectID &id, Fn
if (inst) {
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();
const SLAPrintObject *po =
@ -925,16 +939,12 @@ std::unique_ptr<VirtualBedHandler> VirtualBedHandler::create(const ExtendedBed &
if (is_infinite_bed(bed)) {
ret = std::make_unique<PhysicalOnlyVBedHandler>();
} else {
// The gap between logical beds expressed in ratio of
// the current bed width.
constexpr double LogicalBedGap = 1. / 10.;
BedsGrid::Gap gap;
visit_bed([&gap](auto &rawbed) { gap = bed_gap(rawbed); }, bed);
BoundingBox bedbb;
visit_bed([&bedbb](auto &rawbed) { bedbb = bounding_box(rawbed); }, bed);
auto bedwidth = bedbb.size().x();
coord_t xgap = LogicalBedGap * bedwidth;
ret = std::make_unique<GridStriderVBedHandler>(bedbb, xgap);
ret = std::make_unique<GridStriderVBedHandler>(bedbb, gap);
}
return ret;

View File

@ -42,7 +42,7 @@ class DynamicPrintConfig;
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
// 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(const Arrangeable &)>) const = 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
@ -110,7 +111,7 @@ public:
virtual std::vector<bool> selected_objects() 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
@ -138,7 +139,7 @@ public:
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
@ -148,13 +149,16 @@ struct ArrangeableWipeTowerBase: public Arrangeable
Polygon poly;
SelectionPredicate selection_pred;
int bed_index{0};
ArrangeableWipeTowerBase(
const ObjectID &objid,
Polygon shape,
SelectionPredicate selection_predicate = [] { return false; })
int bed_index,
SelectionPredicate selection_predicate = [](int){ return false; })
: oid{objid},
poly{std::move(shape)},
bed_index{bed_index},
selection_pred{std::move(selection_predicate)}
{}
@ -174,7 +178,7 @@ struct ArrangeableWipeTowerBase: public Arrangeable
bool is_selected() const override
{
return selection_pred();
return selection_pred(bed_index);
}
int get_bed_index() const override;
@ -182,6 +186,10 @@ struct ArrangeableWipeTowerBase: public Arrangeable
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 imbue_data(AnyWritable &datastore) const override
@ -194,15 +202,18 @@ class SceneBuilder;
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
// hierarchy
class ArrangeableSlicerModel: public ArrangeableModel
{
protected:
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<const SelectionMask> m_selmask; // Determines which objects are selected/unselected
BedConstraints m_bed_constraints;
private:
friend class SceneBuilder;
@ -234,7 +245,8 @@ class SceneBuilder: public SceneBuilderBase<SceneBuilder>
{
protected:
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<const SelectionMask> m_selection;
@ -259,18 +271,18 @@ public:
using SceneBuilderBase<SceneBuilder>::set_bed;
SceneBuilder &&set_bed(const DynamicPrintConfig &cfg);
SceneBuilder &&set_bed(const Print &print);
SceneBuilder &&set_bed(const DynamicPrintConfig &cfg, const BedsGrid::Gap &gap);
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);
}
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);
}
@ -295,13 +307,6 @@ public:
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.
class PhysicalOnlyVBedHandler final : public VirtualBedHandler
{
@ -368,29 +373,15 @@ public:
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;
YStriderVBedHandler m_ystrider;
public:
GridStriderVBedHandler(const BoundingBox &bedbb,
coord_t gap)
: m_xstrider{bedbb, gap}
, m_ystrider{bedbb, gap}
GridStriderVBedHandler(const BoundingBox &bedbb, const BedsGrid::Gap &gap)
: m_xstrider{bedbb, gap.x()}
, m_ystrider{bedbb, gap.y()}
{}
Vec2i raw2grid(int bedidx) const;
int grid2raw(const Vec2i &crd) const;
int get_bed_index(const VBedPlaceable &obj) const override;
bool assign_bed(VBedPlaceable &inst, int bed_idx) override;
@ -451,13 +442,15 @@ class ArrangeableModelInstance : public Arrangeable, VBedPlaceable
VBedHPtr *m_vbedh;
const SelectionMask *m_selmask;
InstPos m_pos_within_model;
std::optional<int> m_bed_constraint;
public:
explicit ArrangeableModelInstance(InstPtr *mi,
VBedHPtr *vbedh,
const SelectionMask *selmask,
const InstPos &pos)
: m_mi{mi}, m_vbedh{vbedh}, m_selmask{selmask}, m_pos_within_model{pos}
const InstPos &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);
}
@ -474,6 +467,8 @@ public:
int get_bed_index() const override { return m_vbedh->get_bed_index(*this); }
bool assign_bed(int bed_idx) override;
std::optional<int> bed_constraint() const override { return m_bed_constraint; }
// VBedPlaceable:
BoundingBoxf bounding_box() const override { return to_2d(instance_bounding_box(*m_mi)); }
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 {
Vec<2, size_t> segments = Vec<2, size_t>::Ones();
BoundingBox bb;
BedsGrid::Gap gap;
RectPivots pivot = RectPivots::Center;
SegmentedRectangleBed() = default;
SegmentedRectangleBed(const BoundingBox &bb,
size_t segments_x,
size_t segments_y,
const BedsGrid::Gap &gap,
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(); }
@ -41,13 +43,16 @@ struct SegmentedRectangleBed<std::integral_constant<size_t, SegX>,
std::integral_constant<size_t, SegY>>
{
BoundingBox bb;
BedsGrid::Gap gap;
RectPivots pivot = RectPivots::Center;
SegmentedRectangleBed() = default;
explicit SegmentedRectangleBed(const BoundingBox &b,
const BedsGrid::Gap &gap,
const RectPivots pivot = RectPivots::Center)
: bb{b}
: bb{b},
gap{gap}
{}
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>>
{
BoundingBox bb;
BedsGrid::Gap gap;
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_y() const noexcept { return SegY; }
@ -92,6 +98,12 @@ auto bounding_box(const SegmentedRectangleBed<Args...> &bed)
return bed.bb;
}
template<class...Args>
auto bed_gap(const SegmentedRectangleBed<Args...> &bed)
{
return bed.gap;
}
template<class...Args>
auto area(const SegmentedRectangleBed<Args...> &bed)
{

View File

@ -10,6 +10,8 @@
#include <boost/log/trivial.hpp>
#include "ArrangeTask.hpp"
#include "libslic3r/Arrange/Items/ArrangeItem.hpp"
#include "libslic3r/SVG.hpp"
namespace Slic3r { namespace arr2 {
@ -42,12 +44,6 @@ void extract_selected(ArrangeTask<ArrItem> &task,
<< "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>

View File

@ -21,8 +21,8 @@ int calculate_items_needed_to_fill_bed(const ExtendedBed &bed,
{
double poly_area = fixed_area(prototype_item);
auto area_sum_fn = [](double s, const auto &itm) {
return s + (get_bed_index(itm) == 0) * fixed_area(itm);
auto area_sum_fn = [&](double s, const auto &itm) {
return s + (get_bed_index(itm) == get_bed_constraint(prototype_item)) * fixed_area(itm);
};
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);
});
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);
auto collect_task_items = [&prototype_geometry_id, &task,
&itm_conv](const Arrangeable &arrbl) {
&itm_conv, &bed_constraint](const Arrangeable &arrbl) {
try {
if (arrbl.geometry_id() == prototype_geometry_id) {
if (arrbl.is_printable()) {
auto itm = itm_conv.convert(arrbl);
raise_priority(itm);
task.selected.emplace_back(std::move(itm));
if (arrbl.bed_constraint() == bed_constraint) {
if (arrbl.geometry_id() == prototype_geometry_id) {
if (arrbl.is_printable()) {
auto itm = itm_conv.convert(arrbl);
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) {
BOOST_LOG_TRIVIAL(error)
@ -170,7 +174,7 @@ std::unique_ptr<FillBedTaskResult> FillBedTask<ArrItem>::process_native(
void on_packed(ArrItem &itm) override
{
// 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);
@ -194,12 +198,13 @@ std::unique_ptr<FillBedTaskResult> FillBedTask<ArrItem>::process_native(
auto to_add_range = Range{selected.begin() + selected_existing_count,
selected.end()};
for (auto &itm : to_add_range)
if (get_bed_index(itm) == PhysicalBedId)
for (auto &itm : to_add_range) {
if (get_bed_index(itm) == get_bed_constraint(itm))
result->add_new_item(itm);
}
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);
return result;

View File

@ -130,6 +130,16 @@ const ModelWipeTower& Model::wipe_tower() const
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()
{
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();
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; }
const std::vector<ModelWipeTower>& get_wipe_tower_vector() const { return wipe_tower_vector; }
CustomGCode::Info& custom_gcode_per_print_z();
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_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
{
// 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)
return Vec3d::Zero();
int x = 0;
@ -30,28 +99,13 @@ Vec3d MultipleBeds::get_bed_translation(int id) const
if (m_layout_linear)
x = id;
else {
// Grid layout.
++id;
int a = 1;
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;
BedsGrid::GridCoords coords{BedsGrid::index2grid_coords(id)};
x = coords.x();
y = coords.y();
}
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()
{
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);
}
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!
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)
{

View File

@ -17,6 +17,14 @@ class Print;
extern bool s_reload_preview_after_switching_beds;
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 {
public:
MultipleBeds() = default;
@ -27,13 +35,14 @@ public:
void clear_inst_map();
void set_instance_bed(ObjectID id, int bed_idx);
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; }
bool should_show_next_bed() const { return m_show_next_bed; }
void request_next_bed(bool show);
int get_active_bed() const { return m_active_bed; }
void set_active_bed(int i);
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; }
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);
void set_loading_project_flag(bool project) { m_loading_project = 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);
private:
bool is_instance_on_active_bed(ObjectID id) const;
int m_number_of_beds = 1;
int m_active_bed = 0;
int m_bed_for_thumbnails_generation = -1;
@ -70,13 +79,17 @@ private:
BoundingBoxf m_build_volume_bb_incl_model;
bool m_layout_linear = 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;
} // namespace Slic3r
#endif // libslic3r_MultipleBeds_hpp_

View File

@ -248,7 +248,6 @@ GLVolume::GLVolume(float r, float g, float b, float a)
, is_outside(false)
, hover(HS_None)
, is_modifier(false)
, is_wipe_tower(false)
, is_extrusion_path(false)
, force_native_color(false)
, force_neutral_color(false)
@ -500,11 +499,11 @@ int GLVolumeCollection::load_object_volume(
}
#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 rotation_angle, bool size_unknown, float brim_width, size_t idx, TriangleMesh* out_mesh)
#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 rotation_angle, bool size_unknown, float brim_width, size_t idx)
#endif // SLIC3R_OPENGL_ES
@ -591,9 +590,8 @@ int GLVolumeCollection::load_wipe_tower_preview(
mesh.merge(cone_mesh);
}
volumes.emplace_back(new GLVolume(color));
GLVolume& v = *volumes.back();
GLVolume* result{new GLVolume(color)};
GLVolume& v = *result;
#if SLIC3R_OPENGL_ES
if (out_mesh != nullptr)
*out_mesh = mesh;
@ -607,9 +605,10 @@ int GLVolumeCollection::load_wipe_tower_preview(
v.composite_id = GLVolume::CompositeID(INT_MAX - idx, 0, 0);
v.geometry_id.first = 0;
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;
return int(volumes.size() - 1);
return result;
}
// 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 vol_idx = volume.first->volume_idx();
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() &&
type != GLVolumeCollection::ERenderType::Transparent; // to filter out shells (not very nice)
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.z_data", m_print_volume.zs);
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.normal_z", m_slope.normal_z);
@ -1012,7 +1011,7 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con
}
for (GLVolume* volume : volumes) {
if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || volume->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;
int extruder_id = volume->extruder_id - 1;

View File

@ -189,8 +189,6 @@ public:
bool is_outside : 1;
// Wheter or not this volume has been generated from a modifier
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
bool is_extrusion_path : 1;
// 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.
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.
BoundingBoxf3 bounding_box() const {
return this->model.get_bounding_box();
@ -425,10 +430,10 @@ public:
int instance_idx);
#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);
#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);
#endif // SLIC3R_OPENGL_ES

View File

@ -34,7 +34,7 @@ ArrangeSettingsDialogImgui::ArrangeSettingsDialogImgui(
: 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);
@ -131,9 +131,12 @@ void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y)
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();
}
if (current_bed && ImGuiPureWrap::button(_u8L("Arrange bed")) && m_on_arrange_bed_btn) {
m_on_arrange_bed_btn();
}
ImGuiPureWrap::end();
}

View File

@ -17,6 +17,7 @@ class ArrangeSettingsDialogImgui: public arr2::ArrangeSettingsView {
AnyPtr<arr2::ArrangeSettingsDb> m_db;
std::function<void()> m_on_arrange_btn;
std::function<void()> m_on_arrange_bed_btn;
std::function<void()> m_on_reset_btn;
std::function<bool()> m_show_xl_combo_predicate = [] { return true; };
@ -24,7 +25,7 @@ class ArrangeSettingsDialogImgui: public arr2::ArrangeSettingsView {
public:
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)
{
@ -36,6 +37,11 @@ public:
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)
{
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 float brim_width = wipe_tower_data.brim_width;
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,
max_z, config.wipe_tower_cone_angle, wxGetApp().plater()->model().wipe_tower().rotation, false, brim_width, 0);
GLVolume* volume = m_shells.volumes.volumes.back();
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)};
m_shells.volumes.volumes.emplace_back(volume);
volume->color.a(0.25f);
volume->force_native_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_REMOVE_OBJECT, 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_QUESTION_MARK, SimpleEvent);
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
{
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)
@ -1371,6 +1383,9 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas *canvas, Bed3D &bed)
m_arrange_settings_dialog.on_arrange_btn([]{
wxGetApp().plater()->arrange();
});
m_arrange_settings_dialog.on_arrange_bed_btn([]{
wxGetApp().plater()->arrange_current_bed();
});
}
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();
for (unsigned int vol_idx : volumes_idxs) {
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;
int bed_idx = -1;
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);
for (GLVolume* vol : m_volumes.volumes) {
if (vol->is_wipe_tower)
if (vol->is_wipe_tower())
vol->is_active = (visible && mo == nullptr);
else {
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);
if (mvs == nullptr || force_full_scene_refresh) {
// This GLVolume will be released.
if (volume->is_wipe_tower) {
if (volume->is_wipe_tower()) {
#if SLIC3R_OPENGL_ES
m_wipe_tower_meshes.clear();
#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;
if (extruders_count > 1 && wt && !co) {
for (size_t bed_idx = 0; bed_idx < s_multiple_beds.get_number_of_beds(); ++bed_idx) {
for (size_t bed_idx = 0; bed_idx < s_multiple_beds.get_max_beds(); ++bed_idx) {
const Print *print = wxGetApp().plater()->get_fff_prints()[bed_idx].get();
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;
if (depth != 0.) {
#if SLIC3R_OPENGL_ES
if (bed_idx >= m_wipe_tower_meshes.size())
m_wipe_tower_meshes.resize(bed_idx + 1);
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(
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]);
if (bed_idx >= m_wipe_tower_meshes.size())
m_wipe_tower_meshes.resize(bed_idx + 1);
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,
bw, bed_idx, &m_wipe_tower_meshes[bed_idx]);
#else
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(
x, y, w, depth, z_and_depth_pairs, (float)height, ca, a, !is_wipe_tower_step_done,
bw, bed_idx);
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,
bw, bed_idx);
#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);
if (it != volume_idxs_wipe_towers_old.end())
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));
} else {
delete volume;
}
}
}
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();
@ -2917,6 +2942,8 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
case 'b': { zoom_to_bed(); break; }
case 'C':
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': { m_labels.show(!m_labels.is_shown()); m_dirty = true; break; }
case 'G':
@ -3834,7 +3861,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (!m_hover_volume_idxs.empty()) {
// if right clicking on volume, propagate event through callback (shows context menu)
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
{
// forces the selection of the volume
@ -3859,7 +3886,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (!m_mouse.dragging) {
// do not post the event if the user is panning the scene
// 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;
if (post_right_click_event)
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();
}
}
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.
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) {
@ -4101,7 +4128,7 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
for (const GLVolume* v : m_volumes.volumes) {
++v_id;
if (v->is_wipe_tower) {
if (v->is_wipe_tower()) {
if (m_selection.contains_volume(v_id)) {
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) {
@ -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());
}
GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const
std::vector<GLCanvas3D::WipeTowerInfo> GLCanvas3D::get_wipe_tower_infos() const
{
WipeTowerInfo wti;
for (const GLVolume* vol : m_volumes.volumes) {
if (vol->is_wipe_tower) {
wti.m_pos = Vec2d(m_model->wipe_tower().position.x(),
m_model->wipe_tower().position.y());
wti.m_rotation = (M_PI/180.) * m_model->wipe_tower().rotation;
const BoundingBoxf3& bb = vol->bounding_box();
wti.m_bb = BoundingBoxf{to_2d(bb.min), to_2d(bb.max)};
break;
std::vector<WipeTowerInfo> result;
for (size_t bed_idx = 0; bed_idx < s_multiple_beds.get_max_beds(); ++bed_idx) {
if (m_wipe_tower_bounding_boxes[bed_idx]) {
const ModelWipeTower &wipe_tower{m_model->wipe_tower(bed_idx)};
WipeTowerInfo wti;
wti.m_pos = Vec2d(wipe_tower.position.x(), wipe_tower.position.y());
wti.m_rotation = (M_PI/180.) * wipe_tower.rotation;
wti.m_bb = *m_wipe_tower_bounding_boxes[bed_idx];
wti.m_bed_index = bed_idx;
result.push_back(std::move(wti));
}
}
return wti;
return result;
}
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
for (const GLVolume* v : m_volumes.volumes) {
if (v->is_wipe_tower)
if (v->is_wipe_tower())
continue;
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;
}
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;
}
@ -4730,7 +4758,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const
GLVolumePtrs visible_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 (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);
@ -5161,7 +5189,23 @@ bool GLCanvas3D::_init_main_toolbar()
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));
_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))
return false;
@ -6980,6 +7024,11 @@ bool GLCanvas3D::_deactivate_arrange_menu()
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;
}
@ -7022,10 +7071,10 @@ const SLAPrint* GLCanvas3D::sla_print() const
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().rotation = (180. / M_PI) * rot;
wxGetApp().plater()->model().wipe_tower(bed_index).position = pos;
wxGetApp().plater()->model().wipe_tower(bed_index).rotation = (180. / M_PI) * rot;
}
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_REMOVE_OBJECT, 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_QUESTION_MARK, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event<int>); // data: +1 => increase, -1 => decrease
@ -512,6 +513,8 @@ private:
#if SLIC3R_OPENGL_ES
std::vector<TriangleMesh> m_wipe_tower_meshes;
#endif // SLIC3R_OPENGL_ES
std::array<std::optional<BoundingBoxf>, MAX_NUMBER_OF_BEDS> m_wipe_tower_bounding_boxes;
GCodeViewer m_gcode_viewer;
RenderTimer m_render_timer;
@ -904,28 +907,30 @@ public:
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(); }
void set_selected_extruder(int extruder) { m_selected_extruder = extruder;}
class WipeTowerInfo {
protected:
Vec2d m_pos = {NaNd, NaNd};
double m_rotation = 0.;
BoundingBoxf m_bb;
int m_bed_index{0};
friend class GLCanvas3D;
public:
public:
inline operator bool() const {
return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y());
}
inline const Vec2d& pos() const { return m_pos; }
inline double rotation() const { return m_rotation; }
inline const Vec2d bb_size() const { return m_bb.size(); }
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.
Linef3 mouse_ray(const Point& mouse_pos);
@ -1072,7 +1077,7 @@ private:
void _render_sla_slices();
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_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);
// 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);

View File

@ -25,6 +25,7 @@ wxDEFINE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, 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_PASTE, 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_ALL, 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_PASTE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent);

View File

@ -28,9 +28,13 @@ class GUISelectionMask: public arr2::SelectionMask {
public:
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
@ -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)
{
@ -97,9 +186,9 @@ class ArrangeableWT: public arr2::ArrangeableWipeTowerBase
public:
explicit ArrangeableWT(const ObjectID &oid,
const GLCanvas3D::WipeTowerInfo &wti,
std::function<bool()> sel_pred,
std::function<bool(int)> sel_pred,
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_rot{wti.rotation()}
, m_xl_bb{xl_bb}
@ -108,7 +197,7 @@ public:
// Rotation is disabled for wipe tower in arrangement
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
@ -132,12 +221,12 @@ struct WTH : public arr2::WipeTowerHandler
{
GLCanvas3D::WipeTowerInfo wti;
ObjectID oid;
std::function<bool()> sel_pred;
std::function<bool(int)> sel_pred;
BoundingBox xl_bb;
WTH(const ObjectID &objid,
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)}
{}
@ -158,39 +247,77 @@ struct WTH : public arr2::WipeTowerHandler
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);
}
ObjectID get_id() const override {
return this->oid;
}
};
arr2::SceneBuilder build_scene(Plater &plater, ArrangeSelectionMode mode)
{
arr2::SceneBuilder builder;
const int current_bed{s_multiple_beds.get_active_bed()};
if (mode == ArrangeSelectionMode::SelectionOnly) {
auto sel = std::make_unique<GUISelectionMask>(&plater.get_selection());
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());
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) {
wth = std::make_unique<WTH>(wipe_tower_instance_id(0), wti);
}
if (plater.config()) {
builder.set_bed(*plater.config());
if (wth && is_XL_printer(*plater.config())) {
wth->xl_bb = bounding_box(get_bed_shape(*plater.config()));
for (const auto &info : wipe_tower_infos) {
if (info) {
auto handler{std::make_unique<WTH>(wipe_tower_instance_id(info.bed_index()), info)};
if (plater.config() && is_XL_printer(*plater.config())) {
handler->xl_bb = bounding_box(get_bed_shape(*plater.config()));
}
handlers.push_back(std::move(handler));
}
}
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());
if (plater.printer_technology() == ptSLA)

View File

@ -27,7 +27,7 @@ namespace GUI {
class Plater;
enum class ArrangeSelectionMode { SelectionOnly, Full };
enum class ArrangeSelectionMode { SelectionOnly, Full, CurrentBedFull, CurrentBedSelectionOnly };
arr2::SceneBuilder build_scene(
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_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); });
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_QUESTION_MARK, [](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
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, [q](SimpleEvent&) { q->reset_with_confirm(); });
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_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); });
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()
// pulls the correct data.
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->preview->reload_print();
if (force_background_processing_restart)
@ -5410,7 +5413,7 @@ void Plater::fill_bed_with_instances()
};
auto scene = arr2::Scene{
build_scene(*this, ArrangeSelectionMode::SelectionOnly)};
build_scene(*this, ArrangeSelectionMode::CurrentBedSelectionOnly)};
cbs.on_finished = [this](arr2::FillBedTaskResult &result) {
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();
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);
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;
arrange(w, true);
arrange(w, ArrangeSelectionMode::CurrentBedSelectionOnly);
w.wait_for_idle();
}
@ -6737,18 +6743,33 @@ static std::string concat_strings(const std::set<std::string> &strings,
void Plater::arrange()
{
const auto mode{
wxGetKeyState(WXK_SHIFT) ?
ArrangeSelectionMode::SelectionOnly :
ArrangeSelectionMode::Full
};
if (p->can_arrange()) {
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 ?
ArrangeSelectionMode::SelectionOnly :
ArrangeSelectionMode::Full;
const auto mode{
wxGetKeyState(WXK_SHIFT) ?
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)};
ArrangeJob2::Callbacks cbs;
@ -6782,10 +6803,7 @@ void Plater::arrange(Worker &w, bool selected)
concat_strings(names, "\n")));
}
s_multiple_beds.update_after_load_or_arrange(model(), build_volume(), [this]() {
canvas3D()->check_volumes_outside_state();
s_multiple_beds.ensure_wipe_towers_on_beds(model(), get_fff_prints());
});
canvas3D()->check_volumes_outside_state();
update(static_cast<unsigned int>(UpdateParams::FORCE_FULL_SCREEN_REFRESH));
wxGetApp().obj_manipul()->set_dirty();

View File

@ -64,6 +64,7 @@ struct Camera;
class GLToolbar;
class UserAccount;
class PresetArchiveDatabase;
enum class ArrangeSelectionMode;
class Plater: public wxPanel
{
@ -285,9 +286,10 @@ public:
GLCanvas3D* get_current_canvas3D();
void render_sliders(GLCanvas3D& canvas);
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 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();
if (selection.is_single_volume() || selection.is_single_modifier()) {
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();
}
}

View File

@ -148,7 +148,7 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec
return;
// 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;
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
bool needs_reset = as_single_selection && !already_contained;
needs_reset |= volume->is_wipe_tower;
needs_reset |= is_wipe_tower() && !volume->is_wipe_tower;
needs_reset |= 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 |= is_any_modifier() && !volume->is_modifier;
@ -381,7 +381,7 @@ void Selection::add_all()
unsigned int count = 0;
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;
}
@ -394,7 +394,7 @@ void Selection::add_all()
clear();
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);
}
@ -1445,7 +1445,7 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co
if (done.size() == m_volumes->size())
break;
if ((*m_volumes)[i]->is_wipe_tower)
if ((*m_volumes)[i]->is_wipe_tower())
continue;
const int object_idx = (*m_volumes)[i]->object_idx();
@ -1919,7 +1919,7 @@ void Selection::update_type()
m_type = Empty;
else if (m_list.size() == 1) {
const GLVolume* first = (*m_volumes)[*m_list.begin()];
if (first->is_wipe_tower)
if (first->is_wipe_tower())
m_type = WipeTower;
else if (first->is_modifier) {
m_type = SingleModifier;
@ -2740,7 +2740,7 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_
if (done.size() == m_volumes->size())
break;
const GLVolume* volume_i = (*m_volumes)[i];
if (volume_i->is_wipe_tower)
if (volume_i->is_wipe_tower())
continue;
const int object_idx = volume_i->object_idx();
@ -2785,7 +2785,7 @@ void Selection::synchronize_unselected_volumes()
{
for (unsigned int i : m_list) {
const GLVolume* volume = (*m_volumes)[i];
if (volume->is_wipe_tower)
if (volume->is_wipe_tower())
continue;
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) {
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()) {
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());
@ -2840,7 +2840,7 @@ void Selection::ensure_not_below_bed()
for (size_t i = 0; i < m_volumes->size(); ++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 std::pair<int, int> instance = std::make_pair(volume->object_idx(), volume->instance_idx());
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>()
{
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>()
@ -640,6 +640,7 @@ struct RectangleItem {
void set_bed_index(int idx) { bed_index = idx; }
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; }
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::PhysicalOnlyVBedHandler 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());
std::optional<ObjectID> id_returned = arr2::retrieve_id(itm);
@ -330,7 +330,7 @@ auto create_vbed_handler<Slic3r::arr2::YStriderVBedHandler>(const Slic3r::Boundi
template<>
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",
@ -628,7 +628,7 @@ TEMPLATE_TEST_CASE("Bed needs to be completely filled with 1cm cubes",
ModelObject* new_object = m.add_object();
new_object->name = "10mm_box";
new_object->add_instance();
ModelInstance *instance = new_object->add_instance();
TriangleMesh mesh = make_cube(10., 10., 10.);
ModelVolume* new_volume = new_object->add_volume(mesh);
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::BedConstraints constraints;
constraints.insert({instance->id(), 0});
arr2::Scene scene{arr2::SceneBuilder{}
.set_model(m)
.set_arrange_settings(settings)
.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 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);
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
@ -799,7 +803,7 @@ TEST_CASE("Testing arrangement involving virtual beds", "[arrange2][integration]
DynamicPrintConfig cfg;
cfg.load_from_ini(std::string(TEST_DATA_DIR PATH_SEPARATOR) + "default_fff.ini",
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 bedsz = unscaled(bedbb.size());
@ -815,7 +819,7 @@ TEST_CASE("Testing arrangement involving virtual beds", "[arrange2][integration]
arr2::Scene scene{arr2::SceneBuilder{}
.set_model(model)
.set_arrange_settings(settings)
.set_bed(cfg)};
.set_bed(cfg, Point::new_scale(10, 10))};
auto itm_conv = arr2::ArrangeableToItemConverter<arr2::ArrangeItem>::create(scene);
@ -883,7 +887,7 @@ public:
};
class MocWTH : public WipeTowerHandler {
std::function<bool()> m_sel_pred;
std::function<bool(int)> m_sel_pred;
ObjectID m_id;
public:
@ -891,18 +895,22 @@ public:
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);
}
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);
}
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);
}
ObjectID get_id() const override {
return m_id;
}
};
}} // 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")
{
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));
@ -1001,7 +1009,10 @@ TEST_CASE("Test SceneBuilder", "[arrange2][integration]")
arr2::SceneBuilder bld;
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")
{

View File

@ -254,7 +254,7 @@ void init_print(std::vector<TriangleMesh> &&meshes, Slic3r::Print &print, Slic3r
double distance = min_object_distance(config);
arr2::ArrangeSettings arrange_settings{};
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) {
duplicate(model, duplicate_count, bed, arrange_settings);
}

View File

@ -43,7 +43,7 @@ SCENARIO("Model construction", "[Model]") {
}
model_object->add_instance();
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(
min_object_distance(config)));

View File

@ -26,6 +26,7 @@ add_executable(${_TEST_NAME}_tests
test_stl.cpp
test_meshboolean.cpp
test_marchingsquares.cpp
test_multiple_beds.cpp
test_region_expansion.cpp
test_timeutils.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;
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") {
w.push(std::make_unique<ArrangeJob2>(arr2::Scene{
arr2::SceneBuilder{}
.set_model(m)
.set_arrange_settings(&settings)
.set_bed(cfg)}));
.set_bed(cfg, BedsGrid::Gap{0, 0})}));
w.process_events();
@ -126,7 +126,7 @@ TEST_CASE("Basic arrange with cube", "[arrangejob]") {
arr2::Scene scene{arr2::SceneBuilder{}
.set_model(m)
.set_arrange_settings(&settings)
.set_bed(cfg)
.set_bed(cfg, BedsGrid::Gap{0, 0})
.set_selection(&sel)};
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{}
.set_model(m)
.set_arrange_settings(&settings)
.set_bed(cfg)
.set_bed(cfg, BedsGrid::Gap{0, 0})
.set_selection(&sel)};
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{}
.set_model(m)
.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.process_events();
@ -266,7 +266,7 @@ TEST_CASE("Test for modifying model during arrangement", "[arrangejob][fillbedjo
new_volume->name = new_object->name;
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>());
RandomArrangeSettings settings;
@ -278,7 +278,7 @@ TEST_CASE("Test for modifying model during arrangement", "[arrangejob][fillbedjo
arr2::Scene scene{arr2::SceneBuilder{}
.set_model(m)
.set_arrange_settings(&settings)
.set_bed(cfg)};
.set_bed(cfg, BedsGrid::Gap{0, 0})};
ArrangeJob2::Callbacks cbs;
cbs.on_prepared = [&m] (auto &) {