mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-12 10:29:08 +08:00
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:
parent
5b59785456
commit
3e33631abf
@ -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 |
46
resources/icons/arrange_current.svg
Normal file
46
resources/icons/arrange_current.svg
Normal 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 |
@ -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));
|
||||
|
||||
|
@ -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});
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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 ¢er, const Points &points)
|
||||
static CircleBed to_circle(const Point ¢er, 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 ¢er, 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 ¢er, 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;
|
||||
}
|
||||
|
@ -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 {};
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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; }
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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_
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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; }
|
||||
|
@ -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")
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)));
|
||||
|
||||
|
@ -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
|
||||
|
35
tests/libslic3r/test_multiple_beds.cpp
Normal file
35
tests/libslic3r/test_multiple_beds.cpp
Normal 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);
|
||||
}
|
@ -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 &) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user