PrusaSlicer.cpp 36 KB

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