1 #ifdef WIN32
2 // Why?
3 #define _WIN32_WINNT 0x0502
4 // The standard Windows includes.
5 #define WIN32_LEAN_AND_MEAN
6 #define NOMINMAX
7 #include <Windows.h>
8 #include <wchar.h>
9 #ifdef SLIC3R_GUI
10 extern "C"
11 {
12 // Let the NVIDIA and AMD know we want to use their graphics card
13 // on a dual graphics card system.
14 __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
15 __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
16 }
17 #endif /* SLIC3R_GUI */
18 #endif /* WIN32 */
19
20 #include <cstdio>
21 #include <string>
22 #include <cstring>
23 #include <iostream>
24 #include <math.h>
25 #include <boost/algorithm/string/predicate.hpp>
26 #include <boost/filesystem.hpp>
27 #include <boost/nowide/args.hpp>
28 #include <boost/nowide/cenv.hpp>
29 #include <boost/nowide/iostream.hpp>
30 #include <boost/nowide/integration/filesystem.hpp>
31
32 #include "unix/fhs.hpp" // Generated by CMake from ../platform/unix/fhs.hpp.in
33
34 #include "libslic3r/libslic3r.h"
35 #include "libslic3r/Config.hpp"
36 #include "libslic3r/Geometry.hpp"
37 #include "libslic3r/GCode/PostProcessor.hpp"
38 #include "libslic3r/Model.hpp"
39 #include "libslic3r/ModelArrange.hpp"
40 #include "libslic3r/Platform.hpp"
41 #include "libslic3r/Print.hpp"
42 #include "libslic3r/SLAPrint.hpp"
43 #include "libslic3r/TriangleMesh.hpp"
44 #include "libslic3r/Format/AMF.hpp"
45 #include "libslic3r/Format/3mf.hpp"
46 #include "libslic3r/Format/STL.hpp"
47 #include "libslic3r/Format/OBJ.hpp"
48 #include "libslic3r/Format/SL1.hpp"
49 #include "libslic3r/Utils.hpp"
50 #include "libslic3r/Thread.hpp"
51
52 #include "PrusaSlicer.hpp"
53
54 #ifdef SLIC3R_GUI
55 #include "slic3r/GUI/GUI_Init.hpp"
56 #endif /* SLIC3R_GUI */
57
58 using namespace Slic3r;
59
run(int argc,char ** argv)60 int CLI::run(int argc, char **argv)
61 {
62 // Mark the main thread for the debugger and for runtime checks.
63 set_current_thread_name("slic3r_main");
64
65 #ifdef __WXGTK__
66 // On Linux, wxGTK has no support for Wayland, and the app crashes on
67 // startup if gtk3 is used. This env var has to be set explicitly to
68 // instruct the window manager to fall back to X server mode.
69 ::setenv("GDK_BACKEND", "x11", /* replace */ true);
70 #endif
71
72 // Switch boost::filesystem to utf8.
73 try {
74 boost::nowide::nowide_filesystem();
75 } catch (const std::runtime_error& ex) {
76 std::string caption = std::string(SLIC3R_APP_NAME) + " Error";
77 std::string text = std::string("An error occured while setting up locale.\n") + (
78 #if !defined(_WIN32) && !defined(__APPLE__)
79 // likely some linux system
80 "You may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n"
81 #endif
82 SLIC3R_APP_NAME " will now terminate.\n\n") + ex.what();
83 #if defined(_WIN32) && defined(SLIC3R_GUI)
84 if (m_actions.empty())
85 // Empty actions means Slicer is executed in the GUI mode. Show a GUI message.
86 MessageBoxA(NULL, text.c_str(), caption.c_str(), MB_OK | MB_ICONERROR);
87 #endif
88 boost::nowide::cerr << text.c_str() << std::endl;
89 return 1;
90 }
91
92 if (! this->setup(argc, argv))
93 return 1;
94
95 m_extra_config.apply(m_config, true);
96 m_extra_config.normalize_fdm();
97
98 PrinterTechnology printer_technology = Slic3r::printer_technology(m_config);
99
100 bool start_gui = m_actions.empty() &&
101 // cutting transformations are setting an "export" action.
102 std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() &&
103 std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() &&
104 std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end();
105 bool start_as_gcodeviewer =
106 #ifdef _WIN32
107 false;
108 #else
109 // On Unix systems, the prusa-slicer binary may be symlinked to give the application a different meaning.
110 boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer");
111 #endif // _WIN32
112
113 const std::vector<std::string> &load_configs = m_config.option<ConfigOptionStrings>("load", true)->values;
114 const ForwardCompatibilitySubstitutionRule config_substitution_rule = m_config.option<ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>>("config_compatibility", true)->value;
115
116 // load config files supplied via --load
117 for (auto const &file : load_configs) {
118 if (! boost::filesystem::exists(file)) {
119 if (m_config.opt_bool("ignore_nonexistent_config")) {
120 continue;
121 } else {
122 boost::nowide::cerr << "No such file: " << file << std::endl;
123 return 1;
124 }
125 }
126 DynamicPrintConfig config;
127 ConfigSubstitutions config_substitutions;
128 try {
129 config_substitutions = config.load(file, config_substitution_rule);
130 } catch (std::exception &ex) {
131 boost::nowide::cerr << "Error while reading config file \"" << file << "\": " << ex.what() << std::endl;
132 return 1;
133 }
134 if (! config_substitutions.empty()) {
135 boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n";
136 for (const ConfigSubstitution &subst : config_substitutions)
137 boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n";
138 }
139 config.normalize_fdm();
140 PrinterTechnology other_printer_technology = Slic3r::printer_technology(config);
141 if (printer_technology == ptUnknown) {
142 printer_technology = other_printer_technology;
143 } else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) {
144 boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl;
145 return 1;
146 }
147 m_print_config.apply(config);
148 }
149
150 // are we starting as gcodeviewer ?
151 for (auto it = m_actions.begin(); it != m_actions.end(); ++it) {
152 if (*it == "gcodeviewer") {
153 start_gui = true;
154 start_as_gcodeviewer = true;
155 m_actions.erase(it);
156 break;
157 }
158 }
159
160 // Read input file(s) if any.
161 for (const std::string& file : m_input_files)
162 if (is_gcode_file(file) && boost::filesystem::exists(file)) {
163 start_as_gcodeviewer = true;
164 break;
165 }
166 if (!start_as_gcodeviewer) {
167 for (const std::string& file : m_input_files) {
168 if (!boost::filesystem::exists(file)) {
169 boost::nowide::cerr << "No such file: " << file << std::endl;
170 exit(1);
171 }
172 Model model;
173 try {
174 // When loading an AMF or 3MF, config is imported as well, including the printer technology.
175 DynamicPrintConfig config;
176 ConfigSubstitutionContext config_substitutions(config_substitution_rule);
177 //FIXME should we check the version here? // | Model::LoadAttribute::CheckVersion ?
178 model = Model::read_from_file(file, &config, &config_substitutions, Model::LoadAttribute::AddDefaultInstances);
179 PrinterTechnology other_printer_technology = Slic3r::printer_technology(config);
180 if (printer_technology == ptUnknown) {
181 printer_technology = other_printer_technology;
182 }
183 else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) {
184 boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl;
185 return 1;
186 }
187 if (! config_substitutions.substitutions.empty()) {
188 boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n";
189 for (const ConfigSubstitution& subst : config_substitutions.substitutions)
190 boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n";
191 }
192 // config is applied to m_print_config before the current m_config values.
193 config += std::move(m_print_config);
194 m_print_config = std::move(config);
195 }
196 catch (std::exception& e) {
197 boost::nowide::cerr << file << ": " << e.what() << std::endl;
198 return 1;
199 }
200 if (model.objects.empty()) {
201 boost::nowide::cerr << "Error: file is empty: " << file << std::endl;
202 continue;
203 }
204 m_models.push_back(model);
205 }
206 }
207
208 // Apply command line options to a more specific DynamicPrintConfig which provides normalize()
209 // (command line options override --load files)
210 m_print_config.apply(m_extra_config, true);
211 // Normalizing after importing the 3MFs / AMFs
212 m_print_config.normalize_fdm();
213
214 // Initialize full print configs for both the FFF and SLA technologies.
215 FullPrintConfig fff_print_config;
216 SLAFullPrintConfig sla_print_config;
217
218 // Synchronize the default parameters and the ones received on the command line.
219 if (printer_technology == ptFFF) {
220 fff_print_config.apply(m_print_config, true);
221 m_print_config.apply(fff_print_config, true);
222 } else if (printer_technology == ptSLA) {
223 // The default value has to be different from the one in fff mode.
224 sla_print_config.printer_technology.value = ptSLA;
225 sla_print_config.output_filename_format.value = "[input_filename_base].sl1";
226
227 // The default bed shape should reflect the default display parameters
228 // and not the fff defaults.
229 double w = sla_print_config.display_width.getFloat();
230 double h = sla_print_config.display_height.getFloat();
231 sla_print_config.bed_shape.values = { Vec2d(0, 0), Vec2d(w, 0), Vec2d(w, h), Vec2d(0, h) };
232
233 sla_print_config.apply(m_print_config, true);
234 m_print_config.apply(sla_print_config, true);
235 }
236
237 std::string validity = m_print_config.validate();
238 if (!validity.empty()) {
239 boost::nowide::cerr << "error: " << validity << std::endl;
240 return 1;
241 }
242
243 // Loop through transform options.
244 bool user_center_specified = false;
245 Points bed = get_bed_shape(m_print_config);
246 ArrangeParams arrange_cfg;
247 arrange_cfg.min_obj_distance = scaled(min_object_distance(m_print_config));
248
249 for (auto const &opt_key : m_transforms) {
250 if (opt_key == "merge") {
251 Model m;
252 for (auto &model : m_models)
253 for (ModelObject *o : model.objects)
254 m.add_object(*o);
255 // Rearrange instances unless --dont-arrange is supplied
256 if (! m_config.opt_bool("dont_arrange")) {
257 m.add_default_instances();
258 if (this->has_print_action())
259 arrange_objects(m, bed, arrange_cfg);
260 else
261 arrange_objects(m, InfiniteBed{}, arrange_cfg);
262 }
263 m_models.clear();
264 m_models.emplace_back(std::move(m));
265 } else if (opt_key == "duplicate") {
266 for (auto &model : m_models) {
267 const bool all_objects_have_instances = std::none_of(
268 model.objects.begin(), model.objects.end(),
269 [](ModelObject* o){ return o->instances.empty(); }
270 );
271
272 int dups = m_config.opt_int("duplicate");
273 if (!all_objects_have_instances) model.add_default_instances();
274
275 try {
276 if (dups > 1) {
277 // if all input objects have defined position(s) apply duplication to the whole model
278 duplicate(model, size_t(dups), bed, arrange_cfg);
279 } else {
280 arrange_objects(model, bed, arrange_cfg);
281 }
282 } catch (std::exception &ex) {
283 boost::nowide::cerr << "error: " << ex.what() << std::endl;
284 return 1;
285 }
286 }
287 } else if (opt_key == "duplicate_grid") {
288 std::vector<int> &ints = m_config.option<ConfigOptionInts>("duplicate_grid")->values;
289 const int x = ints.size() > 0 ? ints.at(0) : 1;
290 const int y = ints.size() > 1 ? ints.at(1) : 1;
291 const double distance = fff_print_config.duplicate_distance.value;
292 for (auto &model : m_models)
293 model.duplicate_objects_grid(x, y, (distance > 0) ? distance : 6); // TODO: this is not the right place for setting a default
294 } else if (opt_key == "center") {
295 user_center_specified = true;
296 for (auto &model : m_models) {
297 model.add_default_instances();
298 // this affects instances:
299 model.center_instances_around_point(m_config.option<ConfigOptionPoint>("center")->value);
300 // this affects volumes:
301 //FIXME Vojtech: Who knows why the complete model should be aligned with Z as a single rigid body?
302 //model.align_to_ground();
303 BoundingBoxf3 bbox;
304 for (ModelObject *model_object : model.objects)
305 // We are interested into the Z span only, therefore it is sufficient to measure the bounding box of the 1st instance only.
306 bbox.merge(model_object->instance_bounding_box(0, false));
307 for (ModelObject *model_object : model.objects)
308 for (ModelInstance *model_instance : model_object->instances)
309 model_instance->set_offset(Z, model_instance->get_offset(Z) - bbox.min.z());
310 }
311 } else if (opt_key == "align_xy") {
312 const Vec2d &p = m_config.option<ConfigOptionPoint>("align_xy")->value;
313 for (auto &model : m_models) {
314 BoundingBoxf3 bb = model.bounding_box();
315 // this affects volumes:
316 model.translate(-(bb.min.x() - p.x()), -(bb.min.y() - p.y()), -bb.min.z());
317 }
318 } else if (opt_key == "dont_arrange") {
319 // do nothing - this option alters other transform options
320 } else if (opt_key == "rotate") {
321 for (auto &model : m_models)
322 for (auto &o : model.objects)
323 // this affects volumes:
324 o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), Z);
325 } else if (opt_key == "rotate_x") {
326 for (auto &model : m_models)
327 for (auto &o : model.objects)
328 // this affects volumes:
329 o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), X);
330 } else if (opt_key == "rotate_y") {
331 for (auto &model : m_models)
332 for (auto &o : model.objects)
333 // this affects volumes:
334 o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), Y);
335 } else if (opt_key == "scale") {
336 for (auto &model : m_models)
337 for (auto &o : model.objects)
338 // this affects volumes:
339 o->scale(m_config.get_abs_value(opt_key, 1));
340 } else if (opt_key == "scale_to_fit") {
341 const Vec3d &opt = m_config.opt<ConfigOptionPoint3>(opt_key)->value;
342 if (opt.x() <= 0 || opt.y() <= 0 || opt.z() <= 0) {
343 boost::nowide::cerr << "--scale-to-fit requires a positive volume" << std::endl;
344 return 1;
345 }
346 for (auto &model : m_models)
347 for (auto &o : model.objects)
348 // this affects volumes:
349 o->scale_to_fit(opt);
350 } else if (opt_key == "cut" || opt_key == "cut_x" || opt_key == "cut_y") {
351 std::vector<Model> new_models;
352 for (auto &model : m_models) {
353 model.translate(0, 0, -model.bounding_box().min.z()); // align to z = 0
354 size_t num_objects = model.objects.size();
355 for (size_t i = 0; i < num_objects; ++ i) {
356
357 #if 0
358 if (opt_key == "cut_x") {
359 o->cut(X, m_config.opt_float("cut_x"), &out);
360 } else if (opt_key == "cut_y") {
361 o->cut(Y, m_config.opt_float("cut_y"), &out);
362 } else if (opt_key == "cut") {
363 o->cut(Z, m_config.opt_float("cut"), &out);
364 }
365 #else
366 model.objects.front()->cut(0, m_config.opt_float("cut"), true, true, true);
367 #endif
368 model.delete_object(size_t(0));
369 }
370 }
371
372 // TODO: copy less stuff around using pointers
373 m_models = new_models;
374
375 if (m_actions.empty())
376 m_actions.push_back("export_stl");
377 }
378 #if 0
379 else if (opt_key == "cut_grid") {
380 std::vector<Model> new_models;
381 for (auto &model : m_models) {
382 TriangleMesh mesh = model.mesh();
383 mesh.repair();
384
385 TriangleMeshPtrs meshes = mesh.cut_by_grid(m_config.option<ConfigOptionPoint>("cut_grid")->value);
386 size_t i = 0;
387 for (TriangleMesh* m : meshes) {
388 Model out;
389 auto o = out.add_object();
390 o->add_volume(*m);
391 o->input_file += "_" + std::to_string(i++);
392 delete m;
393 }
394 }
395
396 // TODO: copy less stuff around using pointers
397 m_models = new_models;
398
399 if (m_actions.empty())
400 m_actions.push_back("export_stl");
401 }
402 #endif
403 else if (opt_key == "split") {
404 for (Model &model : m_models) {
405 size_t num_objects = model.objects.size();
406 for (size_t i = 0; i < num_objects; ++ i) {
407 model.objects.front()->split(nullptr);
408 model.delete_object(size_t(0));
409 }
410 }
411 } else if (opt_key == "repair") {
412 // Models are repaired by default.
413 //for (auto &model : m_models)
414 // model.repair();
415 } else {
416 boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl;
417 return 1;
418 }
419 }
420
421 // loop through action options
422 for (auto const &opt_key : m_actions) {
423 if (opt_key == "help") {
424 this->print_help();
425 } else if (opt_key == "help_fff") {
426 this->print_help(true, ptFFF);
427 } else if (opt_key == "help_sla") {
428 this->print_help(true, ptSLA);
429 } else if (opt_key == "save") {
430 //FIXME check for mixing the FFF / SLA parameters.
431 // or better save fff_print_config vs. sla_print_config
432 m_print_config.save(m_config.opt_string("save"));
433 } else if (opt_key == "info") {
434 // --info works on unrepaired model
435 for (Model &model : m_models) {
436 model.add_default_instances();
437 model.print_info();
438 }
439 } else if (opt_key == "export_stl") {
440 for (auto &model : m_models)
441 model.add_default_instances();
442 if (! this->export_models(IO::STL))
443 return 1;
444 } else if (opt_key == "export_obj") {
445 for (auto &model : m_models)
446 model.add_default_instances();
447 if (! this->export_models(IO::OBJ))
448 return 1;
449 } else if (opt_key == "export_amf") {
450 if (! this->export_models(IO::AMF))
451 return 1;
452 } else if (opt_key == "export_3mf") {
453 if (! this->export_models(IO::TMF))
454 return 1;
455 } else if (opt_key == "export_gcode" || opt_key == "export_sla" || opt_key == "slice") {
456 if (opt_key == "export_gcode" && printer_technology == ptSLA) {
457 boost::nowide::cerr << "error: cannot export G-code for an FFF configuration" << std::endl;
458 return 1;
459 } else if (opt_key == "export_sla" && printer_technology == ptFFF) {
460 boost::nowide::cerr << "error: cannot export SLA slices for a SLA configuration" << std::endl;
461 return 1;
462 }
463 // Make a copy of the model if the current action is not the last action, as the model may be
464 // modified by the centering and such.
465 Model model_copy;
466 bool make_copy = &opt_key != &m_actions.back();
467 for (Model &model_in : m_models) {
468 if (make_copy)
469 model_copy = model_in;
470 Model &model = make_copy ? model_copy : model_in;
471 // If all objects have defined instances, their relative positions will be
472 // honored when printing (they will be only centered, unless --dont-arrange
473 // is supplied); if any object has no instances, it will get a default one
474 // and all instances will be rearranged (unless --dont-arrange is supplied).
475 std::string outfile = m_config.opt_string("output");
476 Print fff_print;
477 SLAPrint sla_print;
478 SL1Archive sla_archive(sla_print.printer_config());
479 sla_print.set_printer(&sla_archive);
480 sla_print.set_status_callback(
481 [](const PrintBase::SlicingStatus& s)
482 {
483 if(s.percent >= 0) // FIXME: is this sufficient?
484 printf("%3d%s %s\n", s.percent, "% =>", s.text.c_str());
485 });
486
487 PrintBase *print = (printer_technology == ptFFF) ? static_cast<PrintBase*>(&fff_print) : static_cast<PrintBase*>(&sla_print);
488 if (! m_config.opt_bool("dont_arrange")) {
489 if (user_center_specified) {
490 Vec2d c = m_config.option<ConfigOptionPoint>("center")->value;
491 arrange_objects(model, InfiniteBed{scaled(c)}, arrange_cfg);
492 } else
493 arrange_objects(model, bed, arrange_cfg);
494 }
495 if (printer_technology == ptFFF) {
496 for (auto* mo : model.objects)
497 fff_print.auto_assign_extruders(mo);
498 } else {
499 // The default for "output_filename_format" is good for FDM: "[input_filename_base].gcode"
500 // Replace it with a reasonable SLA default.
501 std::string &format = m_print_config.opt_string("output_filename_format", true);
502 if (format == static_cast<const ConfigOptionString*>(m_print_config.def()->get("output_filename_format")->default_value.get())->value)
503 format = "[input_filename_base].SL1";
504 }
505 print->apply(model, m_print_config);
506 std::string err = print->validate();
507 if (! err.empty()) {
508 boost::nowide::cerr << err << std::endl;
509 return 1;
510 }
511 if (print->empty())
512 boost::nowide::cout << "Nothing to print for " << outfile << " . Either the print is empty or no object is fully inside the print volume." << std::endl;
513 else
514 try {
515 std::string outfile_final;
516 print->process();
517 if (printer_technology == ptFFF) {
518 // The outfile is processed by a PlaceholderParser.
519 outfile = fff_print.export_gcode(outfile, nullptr, nullptr);
520 outfile_final = fff_print.print_statistics().finalize_output_path(outfile);
521 } else {
522 outfile = sla_print.output_filepath(outfile);
523 // We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata
524 outfile_final = sla_print.print_statistics().finalize_output_path(outfile);
525 sla_archive.export_print(outfile_final, sla_print);
526 }
527 if (outfile != outfile_final) {
528 if (Slic3r::rename_file(outfile, outfile_final)) {
529 boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl;
530 return 1;
531 }
532 outfile = outfile_final;
533 }
534 // Run the post-processing scripts if defined.
535 run_post_process_scripts(outfile, fff_print.full_print_config());
536 boost::nowide::cout << "Slicing result exported to " << outfile << std::endl;
537 } catch (const std::exception &ex) {
538 boost::nowide::cerr << ex.what() << std::endl;
539 return 1;
540 }
541 /*
542 print.center = ! m_config.has("center")
543 && ! m_config.has("align_xy")
544 && ! m_config.opt_bool("dont_arrange");
545 print.set_model(model);
546
547 // start chronometer
548 typedef std::chrono::high_resolution_clock clock_;
549 typedef std::chrono::duration<double, std::ratio<1> > second_;
550 std::chrono::time_point<clock_> t0{ clock_::now() };
551
552 const std::string outfile = this->output_filepath(model, IO::Gcode);
553 try {
554 print.export_gcode(outfile);
555 } catch (std::runtime_error &e) {
556 boost::nowide::cerr << e.what() << std::endl;
557 return 1;
558 }
559 boost::nowide::cout << "G-code exported to " << outfile << std::endl;
560
561 // output some statistics
562 double duration { std::chrono::duration_cast<second_>(clock_::now() - t0).count() };
563 boost::nowide::cout << std::fixed << std::setprecision(0)
564 << "Done. Process took " << (duration/60) << " minutes and "
565 << std::setprecision(3)
566 << std::fmod(duration, 60.0) << " seconds." << std::endl
567 << std::setprecision(2)
568 << "Filament required: " << print.total_used_filament() << "mm"
569 << " (" << print.total_extruded_volume()/1000 << "cm3)" << std::endl;
570 */
571 }
572 } else {
573 boost::nowide::cerr << "error: option not supported yet: " << opt_key << std::endl;
574 return 1;
575 }
576 }
577
578 if (start_gui) {
579 #ifdef SLIC3R_GUI
580 Slic3r::GUI::GUI_InitParams params;
581 params.argc = argc;
582 params.argv = argv;
583 params.load_configs = load_configs;
584 params.extra_config = std::move(m_extra_config);
585 params.input_files = std::move(m_input_files);
586 params.start_as_gcodeviewer = start_as_gcodeviewer;
587 return Slic3r::GUI::GUI_Run(params);
588 #else // SLIC3R_GUI
589 // No GUI support. Just print out a help.
590 this->print_help(false);
591 // If started without a parameter, consider it to be OK, otherwise report an error code (no action etc).
592 return (argc == 0) ? 0 : 1;
593 #endif // SLIC3R_GUI
594 }
595
596 return 0;
597 }
598
setup(int argc,char ** argv)599 bool CLI::setup(int argc, char **argv)
600 {
601 {
602 Slic3r::set_logging_level(1);
603 const char *loglevel = boost::nowide::getenv("SLIC3R_LOGLEVEL");
604 if (loglevel != nullptr) {
605 if (loglevel[0] >= '0' && loglevel[0] <= '9' && loglevel[1] == 0)
606 set_logging_level(loglevel[0] - '0');
607 else
608 boost::nowide::cerr << "Invalid SLIC3R_LOGLEVEL environment variable: " << loglevel << std::endl;
609 }
610 }
611
612 // Detect the operating system flavor after SLIC3R_LOGLEVEL is set.
613 detect_platform();
614
615 boost::filesystem::path path_to_binary = boost::filesystem::system_complete(argv[0]);
616
617 // Path from the Slic3r binary to its resources.
618 #ifdef __APPLE__
619 // The application is packed in the .dmg archive as 'Slic3r.app/Contents/MacOS/Slic3r'
620 // The resources are packed to 'Slic3r.app/Contents/Resources'
621 boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path() / "../Resources";
622 #elif defined _WIN32
623 // The application is packed in the .zip archive in the root,
624 // The resources are packed to 'resources'
625 // Path from Slic3r binary to resources:
626 boost::filesystem::path path_resources = path_to_binary.parent_path() / "resources";
627 #elif defined SLIC3R_FHS
628 // The application is packaged according to the Linux Filesystem Hierarchy Standard
629 // Resources are set to the 'Architecture-independent (shared) data', typically /usr/share or /usr/local/share
630 boost::filesystem::path path_resources = SLIC3R_FHS_RESOURCES;
631 #else
632 // The application is packed in the .tar.bz archive (or in AppImage) as 'bin/slic3r',
633 // The resources are packed to 'resources'
634 // Path from Slic3r binary to resources:
635 boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path() / "../resources";
636 #endif
637
638 set_resources_dir(path_resources.string());
639 set_var_dir((path_resources / "icons").string());
640 set_local_dir((path_resources / "localization").string());
641
642 // Parse all command line options into a DynamicConfig.
643 // If any option is unsupported, print usage and abort immediately.
644 t_config_option_keys opt_order;
645 if (! m_config.read_cli(argc, argv, &m_input_files, &opt_order)) {
646 // Separate error message reported by the CLI parser from the help.
647 boost::nowide::cerr << std::endl;
648 this->print_help();
649 return false;
650 }
651 // Parse actions and transform options.
652 for (auto const &opt_key : opt_order) {
653 if (cli_actions_config_def.has(opt_key))
654 m_actions.emplace_back(opt_key);
655 else if (cli_transform_config_def.has(opt_key))
656 m_transforms.emplace_back(opt_key);
657 }
658
659 {
660 const ConfigOptionInt *opt_loglevel = m_config.opt<ConfigOptionInt>("loglevel");
661 if (opt_loglevel != 0)
662 set_logging_level(opt_loglevel->value);
663 }
664
665 std::string validity = m_config.validate();
666
667 // Initialize with defaults.
668 for (const t_optiondef_map *options : { &cli_actions_config_def.options, &cli_transform_config_def.options, &cli_misc_config_def.options })
669 for (const std::pair<t_config_option_key, ConfigOptionDef> &optdef : *options)
670 m_config.option(optdef.first, true);
671
672 set_data_dir(m_config.opt_string("datadir"));
673
674 if (!validity.empty()) {
675 boost::nowide::cerr << "error: " << validity << std::endl;
676 return false;
677 }
678
679 return true;
680 }
681
print_help(bool include_print_options,PrinterTechnology printer_technology) const682 void CLI::print_help(bool include_print_options, PrinterTechnology printer_technology) const
683 {
684 boost::nowide::cout
685 << SLIC3R_BUILD_ID << " " << "based on Slic3r"
686 #ifdef SLIC3R_GUI
687 << " (with GUI support)"
688 #else /* SLIC3R_GUI */
689 << " (without GUI support)"
690 #endif /* SLIC3R_GUI */
691 << std::endl
692 << "https://github.com/prusa3d/PrusaSlicer" << std::endl << std::endl
693 << "Usage: prusa-slicer [ ACTIONS ] [ TRANSFORM ] [ OPTIONS ] [ file.stl ... ]" << std::endl
694 << std::endl
695 << "Actions:" << std::endl;
696 cli_actions_config_def.print_cli_help(boost::nowide::cout, false);
697
698 boost::nowide::cout
699 << std::endl
700 << "Transform options:" << std::endl;
701 cli_transform_config_def.print_cli_help(boost::nowide::cout, false);
702
703 boost::nowide::cout
704 << std::endl
705 << "Other options:" << std::endl;
706 cli_misc_config_def.print_cli_help(boost::nowide::cout, false);
707
708 boost::nowide::cout
709 << std::endl
710 << "Print options are processed in the following order:" << std::endl
711 << "\t1) Config keys from the command line, for example --fill-pattern=stars" << std::endl
712 << "\t (highest priority, overwrites everything below)" << std::endl
713 << "\t2) Config files loaded with --load" << std::endl
714 << "\t3) Config values loaded from amf or 3mf files" << std::endl;
715
716 if (include_print_options) {
717 boost::nowide::cout << std::endl;
718 print_config_def.print_cli_help(boost::nowide::cout, true, [printer_technology](const ConfigOptionDef &def)
719 { return printer_technology == ptAny || def.printer_technology == ptAny || printer_technology == def.printer_technology; });
720 } else {
721 boost::nowide::cout
722 << std::endl
723 << "Run --help-fff / --help-sla to see the full listing of print options." << std::endl;
724 }
725 }
726
export_models(IO::ExportFormat format)727 bool CLI::export_models(IO::ExportFormat format)
728 {
729 for (Model &model : m_models) {
730 const std::string path = this->output_filepath(model, format);
731 bool success = false;
732 switch (format) {
733 case IO::AMF: success = Slic3r::store_amf(path.c_str(), &model, nullptr, false); break;
734 case IO::OBJ: success = Slic3r::store_obj(path.c_str(), &model); break;
735 case IO::STL: success = Slic3r::store_stl(path.c_str(), &model, true); break;
736 case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr, false); break;
737 default: assert(false); break;
738 }
739 if (success)
740 std::cout << "File exported to " << path << std::endl;
741 else {
742 std::cerr << "File export to " << path << " failed" << std::endl;
743 return false;
744 }
745 }
746 return true;
747 }
748
output_filepath(const Model & model,IO::ExportFormat format) const749 std::string CLI::output_filepath(const Model &model, IO::ExportFormat format) const
750 {
751 std::string ext;
752 switch (format) {
753 case IO::AMF: ext = ".zip.amf"; break;
754 case IO::OBJ: ext = ".obj"; break;
755 case IO::STL: ext = ".stl"; break;
756 case IO::TMF: ext = ".3mf"; break;
757 default: assert(false); break;
758 };
759 auto proposed_path = boost::filesystem::path(model.propose_export_file_name_and_path(ext));
760 // use --output when available
761 std::string cmdline_param = m_config.opt_string("output");
762 if (! cmdline_param.empty()) {
763 // if we were supplied a directory, use it and append our automatically generated filename
764 boost::filesystem::path cmdline_path(cmdline_param);
765 if (boost::filesystem::is_directory(cmdline_path))
766 proposed_path = cmdline_path / proposed_path.filename();
767 else
768 proposed_path = cmdline_path;
769 }
770 return proposed_path.string();
771 }
772
773 #if defined(_MSC_VER) || defined(__MINGW32__)
774 extern "C" {
slic3r_main(int argc,wchar_t ** argv)775 __declspec(dllexport) int __stdcall slic3r_main(int argc, wchar_t **argv)
776 {
777 // Convert wchar_t arguments to UTF8.
778 std::vector<std::string> argv_narrow;
779 std::vector<char*> argv_ptrs(argc + 1, nullptr);
780 for (size_t i = 0; i < argc; ++ i)
781 argv_narrow.emplace_back(boost::nowide::narrow(argv[i]));
782 for (size_t i = 0; i < argc; ++ i)
783 argv_ptrs[i] = argv_narrow[i].data();
784 // Call the UTF8 main.
785 return CLI().run(argc, argv_ptrs.data());
786 }
787 }
788 #else /* _MSC_VER */
main(int argc,char ** argv)789 int main(int argc, char **argv)
790 {
791 return CLI().run(argc, argv);
792 }
793 #endif /* _MSC_VER */
794