1 #include "SL1.hpp"
2 #include "GCode/ThumbnailData.hpp"
3 #include "libslic3r/Time.hpp"
4 
5 #include <boost/log/trivial.hpp>
6 #include <boost/filesystem.hpp>
7 
8 #include "libslic3r/Zipper.hpp"
9 #include "libslic3r/SLAPrint.hpp"
10 
11 #include <sstream>
12 
13 #include "libslic3r/Exception.hpp"
14 #include "libslic3r/SlicesToTriangleMesh.hpp"
15 #include "libslic3r/MarchingSquares.hpp"
16 #include "libslic3r/ClipperUtils.hpp"
17 #include "libslic3r/MTUtils.hpp"
18 #include "libslic3r/PrintConfig.hpp"
19 #include "libslic3r/SLA/RasterBase.hpp"
20 #include "libslic3r/miniz_extension.hpp"
21 #include "libslic3r/PNGReadWrite.hpp"
22 
23 #include <boost/property_tree/ini_parser.hpp>
24 #include <boost/filesystem/path.hpp>
25 #include <boost/algorithm/string.hpp>
26 
27 namespace marchsq {
28 
29 template<> struct _RasterTraits<Slic3r::png::ImageGreyscale> {
30     using Rst = Slic3r::png::ImageGreyscale;
31 
32     // The type of pixel cell in the raster
33     using ValueType = uint8_t;
34 
35     // Value at a given position
getmarchsq::_RasterTraits36     static uint8_t get(const Rst &rst, size_t row, size_t col)
37     {
38         return rst.get(row, col);
39     }
40 
41     // Number of rows and cols of the raster
rowsmarchsq::_RasterTraits42     static size_t rows(const Rst &rst) { return rst.rows; }
colsmarchsq::_RasterTraits43     static size_t cols(const Rst &rst) { return rst.cols; }
44 };
45 
46 } // namespace marchsq
47 
48 namespace Slic3r {
49 
50 namespace {
51 
52 struct PNGBuffer { std::vector<uint8_t> buf; std::string fname; };
53 struct ArchiveData {
54     boost::property_tree::ptree profile, config;
55     std::vector<PNGBuffer> images;
56 };
57 
58 static const constexpr char *CONFIG_FNAME  = "config.ini";
59 static const constexpr char *PROFILE_FNAME = "prusaslicer.ini";
60 
read_ini(const mz_zip_archive_file_stat & entry,MZ_Archive & zip)61 boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry,
62                                      MZ_Archive &                    zip)
63 {
64     std::string buf(size_t(entry.m_uncomp_size), '\0');
65 
66     if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
67                                            buf.data(), buf.size(), 0))
68         throw Slic3r::FileIOError(zip.get_errorstr());
69 
70     boost::property_tree::ptree tree;
71     std::stringstream ss(buf);
72     boost::property_tree::read_ini(ss, tree);
73     return tree;
74 }
75 
read_png(const mz_zip_archive_file_stat & entry,MZ_Archive & zip,const std::string & name)76 PNGBuffer read_png(const mz_zip_archive_file_stat &entry,
77                    MZ_Archive &                    zip,
78                    const std::string &             name)
79 {
80     std::vector<uint8_t> buf(entry.m_uncomp_size);
81 
82     if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
83                                            buf.data(), buf.size(), 0))
84         throw Slic3r::FileIOError(zip.get_errorstr());
85 
86     return {std::move(buf), (name.empty() ? entry.m_filename : name)};
87 }
88 
extract_sla_archive(const std::string & zipfname,const std::string & exclude)89 ArchiveData extract_sla_archive(const std::string &zipfname,
90                                  const std::string &exclude)
91 {
92     ArchiveData arch;
93 
94     // Little RAII
95     struct Arch: public MZ_Archive {
96         Arch(const std::string &fname) {
97             if (!open_zip_reader(&arch, fname))
98                 throw Slic3r::FileIOError(get_errorstr());
99         }
100 
101         ~Arch() { close_zip_reader(&arch); }
102     } zip (zipfname);
103 
104     mz_uint num_entries = mz_zip_reader_get_num_files(&zip.arch);
105 
106     for (mz_uint i = 0; i < num_entries; ++i)
107     {
108         mz_zip_archive_file_stat entry;
109 
110         if (mz_zip_reader_file_stat(&zip.arch, i, &entry))
111         {
112             std::string name = entry.m_filename;
113             boost::algorithm::to_lower(name);
114 
115             if (boost::algorithm::contains(name, exclude)) continue;
116 
117             if (name == CONFIG_FNAME) arch.config = read_ini(entry, zip);
118             if (name == PROFILE_FNAME) arch.profile = read_ini(entry, zip);
119 
120             if (boost::filesystem::path(name).extension().string() == ".png") {
121                 auto it = std::lower_bound(
122                     arch.images.begin(), arch.images.end(), PNGBuffer{{}, name},
123                     [](const PNGBuffer &r1, const PNGBuffer &r2) {
124                         return std::less<std::string>()(r1.fname, r2.fname);
125                     });
126 
127                 arch.images.insert(it, read_png(entry, zip, name));
128             }
129         }
130     }
131 
132     return arch;
133 }
134 
rings_to_expolygons(const std::vector<marchsq::Ring> & rings,double px_w,double px_h)135 ExPolygons rings_to_expolygons(const std::vector<marchsq::Ring> &rings,
136                                double px_w, double px_h)
137 {
138     ExPolygons polys; polys.reserve(rings.size());
139 
140     for (const marchsq::Ring &ring : rings) {
141         Polygon poly; Points &pts = poly.points;
142         pts.reserve(ring.size());
143 
144         for (const marchsq::Coord &crd : ring)
145             pts.emplace_back(scaled(crd.c * px_w), scaled(crd.r * px_h));
146 
147         polys.emplace_back(poly);
148     }
149 
150     // reverse the raster transformations
151     return union_ex(polys);
152 }
153 
foreach_vertex(ExPolygon & poly,Fn && fn)154 template<class Fn> void foreach_vertex(ExPolygon &poly, Fn &&fn)
155 {
156     for (auto &p : poly.contour.points) fn(p);
157     for (auto &h : poly.holes)
158         for (auto &p : h.points) fn(p);
159 }
160 
invert_raster_trafo(ExPolygons & expolys,const sla::RasterBase::Trafo & trafo,coord_t width,coord_t height)161 void invert_raster_trafo(ExPolygons &                  expolys,
162                          const sla::RasterBase::Trafo &trafo,
163                          coord_t                       width,
164                          coord_t                       height)
165 {
166     if (trafo.flipXY) std::swap(height, width);
167 
168     for (auto &expoly : expolys) {
169         if (trafo.mirror_y)
170             foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); });
171 
172         if (trafo.mirror_x)
173             foreach_vertex(expoly, [width](Point &p) {p.x() = width - p.x(); });
174 
175         expoly.translate(-trafo.center_x, -trafo.center_y);
176 
177         if (trafo.flipXY)
178             foreach_vertex(expoly, [](Point &p) { std::swap(p.x(), p.y()); });
179 
180         if ((trafo.mirror_x + trafo.mirror_y + trafo.flipXY) % 2) {
181             expoly.contour.reverse();
182             for (auto &h : expoly.holes) h.reverse();
183         }
184     }
185 }
186 
187 struct RasterParams {
188     sla::RasterBase::Trafo trafo; // Raster transformations
189     coord_t        width, height; // scaled raster dimensions (not resolution)
190     double         px_h, px_w;    // pixel dimesions
191     marchsq::Coord win;           // marching squares window size
192 };
193 
get_raster_params(const DynamicPrintConfig & cfg)194 RasterParams get_raster_params(const DynamicPrintConfig &cfg)
195 {
196     auto *opt_disp_cols = cfg.option<ConfigOptionInt>("display_pixels_x");
197     auto *opt_disp_rows = cfg.option<ConfigOptionInt>("display_pixels_y");
198     auto *opt_disp_w    = cfg.option<ConfigOptionFloat>("display_width");
199     auto *opt_disp_h    = cfg.option<ConfigOptionFloat>("display_height");
200     auto *opt_mirror_x  = cfg.option<ConfigOptionBool>("display_mirror_x");
201     auto *opt_mirror_y  = cfg.option<ConfigOptionBool>("display_mirror_y");
202     auto *opt_orient    = cfg.option<ConfigOptionEnum<SLADisplayOrientation>>("display_orientation");
203 
204     if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h ||
205         !opt_mirror_x || !opt_mirror_y || !opt_orient)
206         throw Slic3r::FileIOError("Invalid SL1 / SL1S file");
207 
208     RasterParams rstp;
209 
210     rstp.px_w = opt_disp_w->value / (opt_disp_cols->value - 1);
211     rstp.px_h = opt_disp_h->value / (opt_disp_rows->value - 1);
212 
213     rstp.trafo = sla::RasterBase::Trafo{opt_orient->value == sladoLandscape ?
214                                      sla::RasterBase::roLandscape :
215                                      sla::RasterBase::roPortrait,
216                                  {opt_mirror_x->value, opt_mirror_y->value}};
217 
218     rstp.height = scaled(opt_disp_h->value);
219     rstp.width  = scaled(opt_disp_w->value);
220 
221     return rstp;
222 }
223 
224 struct SliceParams { double layerh = 0., initial_layerh = 0.; };
225 
get_slice_params(const DynamicPrintConfig & cfg)226 SliceParams get_slice_params(const DynamicPrintConfig &cfg)
227 {
228     auto *opt_layerh = cfg.option<ConfigOptionFloat>("layer_height");
229     auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("initial_layer_height");
230 
231     if (!opt_layerh || !opt_init_layerh)
232         throw Slic3r::FileIOError("Invalid SL1 / SL1S file");
233 
234     return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()};
235 }
236 
extract_slices_from_sla_archive(ArchiveData & arch,const RasterParams & rstp,std::function<bool (int)> progr)237 std::vector<ExPolygons> extract_slices_from_sla_archive(
238     ArchiveData &            arch,
239     const RasterParams &     rstp,
240     std::function<bool(int)> progr)
241 {
242     auto jobdir = arch.config.get<std::string>("jobDir");
243     for (auto &c : jobdir) c = std::tolower(c);
244 
245     std::vector<ExPolygons> slices(arch.images.size());
246 
247     struct Status
248     {
249         double          incr, val, prev;
250         bool            stop = false;
251         tbb::spin_mutex mutex;
252     } st {100. / slices.size(), 0., 0.};
253 
254     tbb::parallel_for(size_t(0), arch.images.size(),
255                      [&arch, &slices, &st, &rstp, progr](size_t i) {
256         // Status indication guarded with the spinlock
257         {
258             std::lock_guard<tbb::spin_mutex> lck(st.mutex);
259             if (st.stop) return;
260 
261             st.val += st.incr;
262             double curr = std::round(st.val);
263             if (curr > st.prev) {
264                 st.prev = curr;
265                 st.stop = !progr(int(curr));
266             }
267         }
268 
269         png::ImageGreyscale img;
270         png::ReadBuf rb{arch.images[i].buf.data(), arch.images[i].buf.size()};
271         if (!png::decode_png(rb, img)) return;
272 
273         auto rings = marchsq::execute(img, 128, rstp.win);
274         ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, rstp.px_h);
275 
276         // Invert the raster transformations indicated in
277         // the profile metadata
278         invert_raster_trafo(expolys, rstp.trafo, rstp.width, rstp.height);
279 
280         slices[i] = std::move(expolys);
281     });
282 
283     if (st.stop) slices = {};
284 
285     return slices;
286 }
287 
288 } // namespace
289 
import_sla_archive(const std::string & zipfname,DynamicPrintConfig & out)290 ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out)
291 {
292     ArchiveData arch = extract_sla_archive(zipfname, "png");
293     return out.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable);
294 }
295 
import_sla_archive(const std::string & zipfname,Vec2i windowsize,TriangleMesh & out,DynamicPrintConfig & profile,std::function<bool (int)> progr)296 ConfigSubstitutions import_sla_archive(
297     const std::string &      zipfname,
298     Vec2i                    windowsize,
299     TriangleMesh &           out,
300     DynamicPrintConfig &     profile,
301     std::function<bool(int)> progr)
302 {
303     // Ensure minimum window size for marching squares
304     windowsize.x() = std::max(2, windowsize.x());
305     windowsize.y() = std::max(2, windowsize.y());
306 
307     ArchiveData arch = extract_sla_archive(zipfname, "thumbnail");
308     ConfigSubstitutions config_substitutions = profile.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable);
309 
310     RasterParams rstp = get_raster_params(profile);
311     rstp.win          = {windowsize.y(), windowsize.x()};
312 
313     SliceParams slicp = get_slice_params(profile);
314 
315     std::vector<ExPolygons> slices =
316         extract_slices_from_sla_archive(arch, rstp, progr);
317 
318     if (!slices.empty())
319         out = slices_to_triangle_mesh(slices, 0, slicp.layerh, slicp.initial_layerh);
320 
321     return config_substitutions;
322 }
323 
324 using ConfMap = std::map<std::string, std::string>;
325 
326 namespace {
327 
to_ini(const ConfMap & m)328 std::string to_ini(const ConfMap &m)
329 {
330     std::string ret;
331     for (auto &param : m) ret += param.first + " = " + param.second + "\n";
332 
333     return ret;
334 }
335 
get_cfg_value(const DynamicPrintConfig & cfg,const std::string & key)336 std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key)
337 {
338     std::string ret;
339 
340     if (cfg.has(key)) {
341         auto opt = cfg.option(key);
342         if (opt) ret = opt->serialize();
343     }
344 
345     return ret;
346 }
347 
fill_iniconf(ConfMap & m,const SLAPrint & print)348 void fill_iniconf(ConfMap &m, const SLAPrint &print)
349 {
350     auto &cfg = print.full_print_config();
351     m["layerHeight"]    = get_cfg_value(cfg, "layer_height");
352     m["expTime"]        = get_cfg_value(cfg, "exposure_time");
353     m["expTimeFirst"]   = get_cfg_value(cfg, "initial_exposure_time");
354     m["materialName"]   = get_cfg_value(cfg, "sla_material_settings_id");
355     m["printerModel"]   = get_cfg_value(cfg, "printer_model");
356     m["printerVariant"] = get_cfg_value(cfg, "printer_variant");
357     m["printerProfile"] = get_cfg_value(cfg, "printer_settings_id");
358     m["printProfile"]   = get_cfg_value(cfg, "sla_print_settings_id");
359     m["fileCreationTimestamp"] = Utils::utc_timestamp();
360     m["prusaSlicerVersion"]    = SLIC3R_BUILD_ID;
361 
362     SLAPrintStatistics stats = print.print_statistics();
363     // Set statistics values to the printer
364 
365     double used_material = (stats.objects_used_material +
366                             stats.support_used_material) / 1000;
367 
368     int num_fade = print.default_object_config().faded_layers.getInt();
369     num_fade = num_fade >= 0 ? num_fade : 0;
370 
371     m["usedMaterial"] = std::to_string(used_material);
372     m["numFade"]      = std::to_string(num_fade);
373     m["numSlow"]      = std::to_string(stats.slow_layers_count);
374     m["numFast"]      = std::to_string(stats.fast_layers_count);
375     m["printTime"]    = std::to_string(stats.estimated_print_time);
376 
377     m["action"] = "print";
378 }
379 
fill_slicerconf(ConfMap & m,const SLAPrint & print)380 void fill_slicerconf(ConfMap &m, const SLAPrint &print)
381 {
382     using namespace std::literals::string_view_literals;
383 
384     // Sorted list of config keys, which shall not be stored into the ini.
385     static constexpr auto banned_keys = {
386 		"compatible_printers"sv,
387         "compatible_prints"sv,
388         //FIXME The print host keys should not be exported to full_print_config anymore. The following keys may likely be removed.
389         "print_host"sv,
390         "printhost_apikey"sv,
391         "printhost_cafile"sv
392     };
393 
394     assert(std::is_sorted(banned_keys.begin(), banned_keys.end()));
395     auto is_banned = [](const std::string &key) {
396         return std::binary_search(banned_keys.begin(), banned_keys.end(), key);
397     };
398 
399     auto &cfg = print.full_print_config();
400     for (const std::string &key : cfg.keys())
401         if (! is_banned(key) && ! cfg.option(key)->is_nil())
402             m[key] = cfg.opt_serialize(key);
403 
404 }
405 
406 } // namespace
407 
create_raster() const408 uqptr<sla::RasterBase> SL1Archive::create_raster() const
409 {
410     sla::RasterBase::Resolution res;
411     sla::RasterBase::PixelDim   pxdim;
412     std::array<bool, 2>         mirror;
413 
414     double w  = m_cfg.display_width.getFloat();
415     double h  = m_cfg.display_height.getFloat();
416     auto   pw = size_t(m_cfg.display_pixels_x.getInt());
417     auto   ph = size_t(m_cfg.display_pixels_y.getInt());
418 
419     mirror[X] = m_cfg.display_mirror_x.getBool();
420     mirror[Y] = m_cfg.display_mirror_y.getBool();
421 
422     auto ro = m_cfg.display_orientation.getInt();
423     sla::RasterBase::Orientation orientation =
424         ro == sla::RasterBase::roPortrait ? sla::RasterBase::roPortrait :
425                                             sla::RasterBase::roLandscape;
426 
427     if (orientation == sla::RasterBase::roPortrait) {
428         std::swap(w, h);
429         std::swap(pw, ph);
430     }
431 
432     res   = sla::RasterBase::Resolution{pw, ph};
433     pxdim = sla::RasterBase::PixelDim{w / pw, h / ph};
434     sla::RasterBase::Trafo tr{orientation, mirror};
435 
436     double gamma = m_cfg.gamma_correction.getFloat();
437 
438     return sla::create_raster_grayscale_aa(res, pxdim, gamma, tr);
439 }
440 
get_encoder() const441 sla::RasterEncoder SL1Archive::get_encoder() const
442 {
443     return sla::PNGRasterEncoder{};
444 }
445 
export_print(Zipper & zipper,const SLAPrint & print,const std::string & prjname)446 void SL1Archive::export_print(Zipper& zipper,
447                               const SLAPrint &print,
448                               const std::string &prjname)
449 {
450     std::string project =
451         prjname.empty() ?
452             boost::filesystem::path(zipper.get_filename()).stem().string() :
453             prjname;
454 
455     ConfMap iniconf, slicerconf;
456     fill_iniconf(iniconf, print);
457 
458     iniconf["jobDir"] = project;
459 
460     fill_slicerconf(slicerconf, print);
461 
462     try {
463         zipper.add_entry("config.ini");
464         zipper << to_ini(iniconf);
465         zipper.add_entry("prusaslicer.ini");
466         zipper << to_ini(slicerconf);
467 
468         size_t i = 0;
469         for (const sla::EncodedRaster &rst : m_layers) {
470 
471             std::string imgname = project + string_printf("%.5d", i++) + "." +
472                                   rst.extension();
473 
474             zipper.add_entry(imgname.c_str(), rst.data(), rst.size());
475         }
476     } catch(std::exception& e) {
477         BOOST_LOG_TRIVIAL(error) << e.what();
478         // Rethrow the exception
479         throw;
480     }
481 }
482 
483 } // namespace Slic3r
484