1 #include "ArrangeJob.hpp"
2 
3 #include "libslic3r/MTUtils.hpp"
4 #include "libslic3r/Model.hpp"
5 
6 #include "slic3r/GUI/Plater.hpp"
7 #include "slic3r/GUI/GLCanvas3D.hpp"
8 #include "slic3r/GUI/GUI.hpp"
9 
10 namespace Slic3r { namespace GUI {
11 
12 // Cache the wti info
13 class WipeTower: public GLCanvas3D::WipeTowerInfo {
14     using ArrangePolygon = arrangement::ArrangePolygon;
15 public:
WipeTower(const GLCanvas3D::WipeTowerInfo & wti)16     explicit WipeTower(const GLCanvas3D::WipeTowerInfo &wti)
17         : GLCanvas3D::WipeTowerInfo(wti)
18     {}
19 
WipeTower(GLCanvas3D::WipeTowerInfo && wti)20     explicit WipeTower(GLCanvas3D::WipeTowerInfo &&wti)
21         : GLCanvas3D::WipeTowerInfo(std::move(wti))
22     {}
23 
apply_arrange_result(const Vec2d & tr,double rotation)24     void apply_arrange_result(const Vec2d& tr, double rotation)
25     {
26         m_pos = unscaled(tr); m_rotation = rotation;
27         apply_wipe_tower();
28     }
29 
get_arrange_polygon() const30     ArrangePolygon get_arrange_polygon() const
31     {
32         Polygon ap({
33             {scaled(m_bb.min)},
34             {scaled(m_bb.max.x()), scaled(m_bb.min.y())},
35             {scaled(m_bb.max)},
36             {scaled(m_bb.min.x()), scaled(m_bb.max.y())}
37             });
38 
39         ArrangePolygon ret;
40         ret.poly.contour = std::move(ap);
41         ret.translation  = scaled(m_pos);
42         ret.rotation     = m_rotation;
43         ++ret.priority;
44 
45         return ret;
46     }
47 };
48 
get_wipe_tower(const Plater & plater)49 static WipeTower get_wipe_tower(const Plater &plater)
50 {
51     return WipeTower{plater.canvas3D()->get_wipe_tower_info()};
52 }
53 
clear_input()54 void ArrangeJob::clear_input()
55 {
56     const Model &model = m_plater->model();
57 
58     size_t count = 0, cunprint = 0; // To know how much space to reserve
59     for (auto obj : model.objects)
60         for (auto mi : obj->instances)
61             mi->printable ? count++ : cunprint++;
62 
63     m_selected.clear();
64     m_unselected.clear();
65     m_unprintable.clear();
66     m_selected.reserve(count + 1 /* for optional wti */);
67     m_unselected.reserve(count + 1 /* for optional wti */);
68     m_unprintable.reserve(cunprint /* for optional wti */);
69 }
70 
prepare_all()71 void ArrangeJob::prepare_all() {
72     clear_input();
73 
74     for (ModelObject *obj: m_plater->model().objects)
75         for (ModelInstance *mi : obj->instances) {
76             ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable;
77             cont.emplace_back(get_arrange_poly(PtrWrapper{mi}, m_plater));
78         }
79 
80     if (auto wti = get_wipe_tower_arrangepoly(*m_plater))
81         m_selected.emplace_back(std::move(*wti));
82 }
83 
prepare_selected()84 void ArrangeJob::prepare_selected() {
85     clear_input();
86 
87     Model &model = m_plater->model();
88     double stride = bed_stride(m_plater);
89 
90     std::vector<const Selection::InstanceIdxsList *>
91             obj_sel(model.objects.size(), nullptr);
92 
93     for (auto &s : m_plater->get_selection().get_content())
94         if (s.first < int(obj_sel.size()))
95             obj_sel[size_t(s.first)] = &s.second;
96 
97     // Go through the objects and check if inside the selection
98     for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
99         const Selection::InstanceIdxsList * instlist = obj_sel[oidx];
100         ModelObject *mo = model.objects[oidx];
101 
102         std::vector<bool> inst_sel(mo->instances.size(), false);
103 
104         if (instlist)
105             for (auto inst_id : *instlist)
106                 inst_sel[size_t(inst_id)] = true;
107 
108         for (size_t i = 0; i < inst_sel.size(); ++i) {
109             ArrangePolygon &&ap =
110                 get_arrange_poly(PtrWrapper{mo->instances[i]}, m_plater);
111 
112             ArrangePolygons &cont = mo->instances[i]->printable ?
113                         (inst_sel[i] ? m_selected :
114                                        m_unselected) :
115                         m_unprintable;
116 
117             cont.emplace_back(std::move(ap));
118         }
119     }
120 
121     if (auto wti = get_wipe_tower(*m_plater)) {
122         ArrangePolygon &&ap = get_arrange_poly(wti, m_plater);
123 
124         auto &cont = m_plater->get_selection().is_wipe_tower() ? m_selected :
125                                                                  m_unselected;
126         cont.emplace_back(std::move(ap));
127     }
128 
129     // If the selection was empty arrange everything
130     if (m_selected.empty()) m_selected.swap(m_unselected);
131 
132     // The strides have to be removed from the fixed items. For the
133     // arrangeable (selected) items bed_idx is ignored and the
134     // translation is irrelevant.
135     for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride;
136 }
137 
prepare()138 void ArrangeJob::prepare()
139 {
140     wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all();
141 }
142 
process()143 void ArrangeJob::process()
144 {
145     static const auto arrangestr = _(L("Arranging"));
146 
147     const GLCanvas3D::ArrangeSettings &settings =
148         static_cast<const GLCanvas3D*>(m_plater->canvas3D())->get_arrange_settings();
149 
150     arrangement::ArrangeParams params;
151     params.allow_rotations  = settings.enable_rotation;
152     params.min_obj_distance = scaled(settings.distance);
153 
154 
155     auto count = unsigned(m_selected.size() + m_unprintable.size());
156     Points bedpts = get_bed_shape(*m_plater->config());
157 
158     params.stopcondition = [this]() { return was_canceled(); };
159 
160     try {
161         params.progressind = [this, count](unsigned st) {
162             st += m_unprintable.size();
163             if (st > 0) update_status(int(count - st), arrangestr);
164         };
165 
166         arrangement::arrange(m_selected, m_unselected, bedpts, params);
167 
168         params.progressind = [this, count](unsigned st) {
169             if (st > 0) update_status(int(count - st), arrangestr);
170         };
171 
172         arrangement::arrange(m_unprintable, {}, bedpts, params);
173     } catch (std::exception & /*e*/) {
174         GUI::show_error(m_plater,
175                         _(L("Could not arrange model objects! "
176                             "Some geometries may be invalid.")));
177     }
178 
179     // finalize just here.
180     update_status(int(count),
181                   was_canceled() ? _(L("Arranging canceled."))
182                                    : _(L("Arranging done.")));
183 }
184 
finalize()185 void ArrangeJob::finalize() {
186     // Ignore the arrange result if aborted.
187     if (was_canceled()) return;
188 
189     // Unprintable items go to the last virtual bed
190     int beds = 0;
191 
192     // Apply the arrange result to all selected objects
193     for (ArrangePolygon &ap : m_selected) {
194         beds = std::max(ap.bed_idx, beds);
195         ap.apply();
196     }
197 
198     // Get the virtual beds from the unselected items
199     for (ArrangePolygon &ap : m_unselected)
200         beds = std::max(ap.bed_idx, beds);
201 
202     // Move the unprintable items to the last virtual bed.
203     for (ArrangePolygon &ap : m_unprintable) {
204         ap.bed_idx += beds + 1;
205         ap.apply();
206     }
207 
208     m_plater->update();
209 
210     Job::finalize();
211 }
212 
213 std::optional<arrangement::ArrangePolygon>
get_wipe_tower_arrangepoly(const Plater & plater)214 get_wipe_tower_arrangepoly(const Plater &plater)
215 {
216     if (auto wti = get_wipe_tower(plater))
217         return get_arrange_poly(wti, &plater);
218 
219     return {};
220 }
221 
bed_stride(const Plater * plater)222 double bed_stride(const Plater *plater) {
223     double bedwidth = plater->bed_shape_bb().size().x();
224     return scaled<double>((1. + LOGICAL_BED_GAP) * bedwidth);
225 }
226 
227 }} // namespace Slic3r::GUI
228