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