#ifdef WIN32 // Why? #define _WIN32_WINNT 0x0502 // The standard Windows includes. #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include #include #ifdef SLIC3R_GUI extern "C" { // Let the NVIDIA and AMD know we want to use their graphics card // on a dual graphics card system. __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } #endif /* SLIC3R_GUI */ #endif /* WIN32 */ #include #include #include #include #include #include #include #include #include #include #include #include #include "unix/fhs.hpp" // Generated by CMake from ../platform/unix/fhs.hpp.in #include "libslic3r/libslic3r.h" #include "libslic3r/Config.hpp" #include "libslic3r/Geometry.hpp" #include "libslic3r/GCode/PostProcessor.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/ModelArrange.hpp" #include "libslic3r/Platform.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/Format/AMF.hpp" #include "libslic3r/Format/3mf.hpp" #include "libslic3r/Format/STL.hpp" #include "libslic3r/Format/OBJ.hpp" #include "libslic3r/Format/SL1.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Thread.hpp" #include "libslic3r/BlacklistedLibraryCheck.hpp" #include "PrusaSlicer.hpp" #ifdef SLIC3R_GUI #include "slic3r/GUI/GUI_Init.hpp" #endif /* SLIC3R_GUI */ using namespace Slic3r; static PrinterTechnology get_printer_technology(const DynamicConfig &config) { const ConfigOptionEnum *opt = config.option>("printer_technology"); return (opt == nullptr) ? ptUnknown : opt->value; } int CLI::run(int argc, char **argv) { // Mark the main thread for the debugger and for runtime checks. set_current_thread_name("slic3r_main"); #ifdef __WXGTK__ // On Linux, wxGTK has no support for Wayland, and the app crashes on // startup if gtk3 is used. This env var has to be set explicitly to // instruct the window manager to fall back to X server mode. ::setenv("GDK_BACKEND", "x11", /* replace */ true); #endif // Switch boost::filesystem to utf8. try { boost::nowide::nowide_filesystem(); } catch (const std::runtime_error& ex) { std::string caption = std::string(SLIC3R_APP_NAME) + " Error"; std::string text = std::string("An error occured while setting up locale.\n") + ( #if !defined(_WIN32) && !defined(__APPLE__) // likely some linux system "You may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n" #endif SLIC3R_APP_NAME " will now terminate.\n\n") + ex.what(); #if defined(_WIN32) && defined(SLIC3R_GUI) if (m_actions.empty()) // Empty actions means Slicer is executed in the GUI mode. Show a GUI message. MessageBoxA(NULL, text.c_str(), caption.c_str(), MB_OK | MB_ICONERROR); #endif boost::nowide::cerr << text.c_str() << std::endl; return 1; } if (! this->setup(argc, argv)) return 1; m_extra_config.apply(m_config, true); m_extra_config.normalize_fdm(); PrinterTechnology printer_technology = get_printer_technology(m_config); bool start_gui = m_actions.empty() && // cutting transformations are setting an "export" action. std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() && std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() && std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end(); bool start_as_gcodeviewer = #ifdef _WIN32 false; #else // On Unix systems, the prusa-slicer binary may be symlinked to give the application a different meaning. boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer"); #endif // _WIN32 const std::vector &load_configs = m_config.option("load", true)->values; const ForwardCompatibilitySubstitutionRule config_substitution_rule = m_config.option>("config_compatibility", true)->value; // load config files supplied via --load for (auto const &file : load_configs) { if (! boost::filesystem::exists(file)) { if (m_config.opt_bool("ignore_nonexistent_config")) { continue; } else { boost::nowide::cerr << "No such file: " << file << std::endl; return 1; } } DynamicPrintConfig config; ConfigSubstitutions config_substitutions; try { config_substitutions = config.load(file, config_substitution_rule); } catch (std::exception &ex) { boost::nowide::cerr << "Error while reading config file \"" << file << "\": " << ex.what() << std::endl; return 1; } if (! config_substitutions.empty()) { boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n"; for (const ConfigSubstitution &subst : config_substitutions) boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n"; } config.normalize_fdm(); PrinterTechnology other_printer_technology = get_printer_technology(config); if (printer_technology == ptUnknown) { printer_technology = other_printer_technology; } else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) { boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; return 1; } m_print_config.apply(config); } // are we starting as gcodeviewer ? for (auto it = m_actions.begin(); it != m_actions.end(); ++it) { if (*it == "gcodeviewer") { start_gui = true; start_as_gcodeviewer = true; m_actions.erase(it); break; } } // Read input file(s) if any. for (const std::string& file : m_input_files) if (is_gcode_file(file) && boost::filesystem::exists(file)) { start_as_gcodeviewer = true; break; } if (!start_as_gcodeviewer) { for (const std::string& file : m_input_files) { if (!boost::filesystem::exists(file)) { boost::nowide::cerr << "No such file: " << file << std::endl; exit(1); } Model model; try { // When loading an AMF or 3MF, config is imported as well, including the printer technology. DynamicPrintConfig config; ConfigSubstitutionContext config_substitutions(config_substitution_rule); //FIXME should we check the version here? // | Model::LoadAttribute::CheckVersion ? model = Model::read_from_file(file, &config, &config_substitutions, Model::LoadAttribute::AddDefaultInstances); PrinterTechnology other_printer_technology = get_printer_technology(config); if (printer_technology == ptUnknown) { printer_technology = other_printer_technology; } else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) { boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; return 1; } if (! config_substitutions.substitutions.empty()) { boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n"; for (const ConfigSubstitution& subst : config_substitutions.substitutions) boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n"; } // config is applied to m_print_config before the current m_config values. config += std::move(m_print_config); m_print_config = std::move(config); } catch (std::exception& e) { boost::nowide::cerr << file << ": " << e.what() << std::endl; return 1; } if (model.objects.empty()) { boost::nowide::cerr << "Error: file is empty: " << file << std::endl; continue; } m_models.push_back(model); } } // Apply command line options to a more specific DynamicPrintConfig which provides normalize() // (command line options override --load files) m_print_config.apply(m_extra_config, true); // Normalizing after importing the 3MFs / AMFs m_print_config.normalize_fdm(); if (printer_technology == ptUnknown) printer_technology = std::find(m_actions.begin(), m_actions.end(), "export_sla") == m_actions.end() ? ptFFF : ptSLA; m_print_config.option>("printer_technology", true)->value = printer_technology; // Initialize full print configs for both the FFF and SLA technologies. FullPrintConfig fff_print_config; SLAFullPrintConfig sla_print_config; // Synchronize the default parameters and the ones received on the command line. if (printer_technology == ptFFF) { fff_print_config.apply(m_print_config, true); m_print_config.apply(fff_print_config, true); } else { assert(printer_technology == ptSLA); sla_print_config.output_filename_format.value = "[input_filename_base].sl1"; // The default bed shape should reflect the default display parameters // and not the fff defaults. double w = sla_print_config.display_width.getFloat(); double h = sla_print_config.display_height.getFloat(); sla_print_config.bed_shape.values = { Vec2d(0, 0), Vec2d(w, 0), Vec2d(w, h), Vec2d(0, h) }; sla_print_config.apply(m_print_config, true); m_print_config.apply(sla_print_config, true); } { std::string validity = m_print_config.validate(); if (! validity.empty()) { boost::nowide::cerr << "Error: The composite configation is not valid: " << validity << std::endl; return 1; } } // Loop through transform options. bool user_center_specified = false; Points bed = get_bed_shape(m_print_config); ArrangeParams arrange_cfg; arrange_cfg.min_obj_distance = scaled(min_object_distance(m_print_config)); for (auto const &opt_key : m_transforms) { if (opt_key == "merge") { Model m; for (auto &model : m_models) for (ModelObject *o : model.objects) m.add_object(*o); // Rearrange instances unless --dont-arrange is supplied if (! m_config.opt_bool("dont_arrange")) { m.add_default_instances(); if (this->has_print_action()) arrange_objects(m, bed, arrange_cfg); else arrange_objects(m, InfiniteBed{}, arrange_cfg); } m_models.clear(); m_models.emplace_back(std::move(m)); } else if (opt_key == "duplicate") { for (auto &model : m_models) { const bool all_objects_have_instances = std::none_of( model.objects.begin(), model.objects.end(), [](ModelObject* o){ return o->instances.empty(); } ); int dups = m_config.opt_int("duplicate"); if (!all_objects_have_instances) model.add_default_instances(); try { if (dups > 1) { // if all input objects have defined position(s) apply duplication to the whole model duplicate(model, size_t(dups), bed, arrange_cfg); } else { arrange_objects(model, bed, arrange_cfg); } } catch (std::exception &ex) { boost::nowide::cerr << "error: " << ex.what() << std::endl; return 1; } } } else if (opt_key == "duplicate_grid") { std::vector &ints = m_config.option("duplicate_grid")->values; const int x = ints.size() > 0 ? ints.at(0) : 1; const int y = ints.size() > 1 ? ints.at(1) : 1; const double distance = fff_print_config.duplicate_distance.value; for (auto &model : m_models) model.duplicate_objects_grid(x, y, (distance > 0) ? distance : 6); // TODO: this is not the right place for setting a default } else if (opt_key == "center") { user_center_specified = true; for (auto &model : m_models) { model.add_default_instances(); // this affects instances: model.center_instances_around_point(m_config.option("center")->value); // this affects volumes: //FIXME Vojtech: Who knows why the complete model should be aligned with Z as a single rigid body? //model.align_to_ground(); BoundingBoxf3 bbox; for (ModelObject *model_object : model.objects) // We are interested into the Z span only, therefore it is sufficient to measure the bounding box of the 1st instance only. bbox.merge(model_object->instance_bounding_box(0, false)); for (ModelObject *model_object : model.objects) for (ModelInstance *model_instance : model_object->instances) model_instance->set_offset(Z, model_instance->get_offset(Z) - bbox.min.z()); } } else if (opt_key == "align_xy") { const Vec2d &p = m_config.option("align_xy")->value; for (auto &model : m_models) { BoundingBoxf3 bb = model.bounding_box(); // this affects volumes: model.translate(-(bb.min.x() - p.x()), -(bb.min.y() - p.y()), -bb.min.z()); } } else if (opt_key == "dont_arrange") { // do nothing - this option alters other transform options } else if (opt_key == "ensure_on_bed") { // do nothing, the value is used later } else if (opt_key == "rotate") { for (auto &model : m_models) for (auto &o : model.objects) // this affects volumes: o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), Z); } else if (opt_key == "rotate_x") { for (auto &model : m_models) for (auto &o : model.objects) // this affects volumes: o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), X); } else if (opt_key == "rotate_y") { for (auto &model : m_models) for (auto &o : model.objects) // this affects volumes: o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), Y); } else if (opt_key == "scale") { for (auto &model : m_models) for (auto &o : model.objects) // this affects volumes: o->scale(m_config.get_abs_value(opt_key, 1)); } else if (opt_key == "scale_to_fit") { const Vec3d &opt = m_config.opt(opt_key)->value; if (opt.x() <= 0 || opt.y() <= 0 || opt.z() <= 0) { boost::nowide::cerr << "--scale-to-fit requires a positive volume" << std::endl; return 1; } for (auto &model : m_models) for (auto &o : model.objects) // this affects volumes: o->scale_to_fit(opt); } else if (opt_key == "cut" || opt_key == "cut_x" || opt_key == "cut_y") { std::vector new_models; for (auto &model : m_models) { model.translate(0, 0, -model.bounding_box().min.z()); // align to z = 0 size_t num_objects = model.objects.size(); for (size_t i = 0; i < num_objects; ++ i) { #if 0 if (opt_key == "cut_x") { o->cut(X, m_config.opt_float("cut_x"), &out); } else if (opt_key == "cut_y") { o->cut(Y, m_config.opt_float("cut_y"), &out); } else if (opt_key == "cut") { o->cut(Z, m_config.opt_float("cut"), &out); } #else model.objects.front()->cut(0, m_config.opt_float("cut"), ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::FlipLower); #endif model.delete_object(size_t(0)); } } // TODO: copy less stuff around using pointers m_models = new_models; if (m_actions.empty()) m_actions.push_back("export_stl"); } #if 0 else if (opt_key == "cut_grid") { std::vector new_models; for (auto &model : m_models) { TriangleMesh mesh = model.mesh(); mesh.repair(); std::vector meshes = mesh.cut_by_grid(m_config.option("cut_grid")->value); size_t i = 0; for (TriangleMesh* m : meshes) { Model out; auto o = out.add_object(); o->add_volume(*m); o->input_file += "_" + std::to_string(i++); delete m; } } // TODO: copy less stuff around using pointers m_models = new_models; if (m_actions.empty()) m_actions.push_back("export_stl"); } #endif else if (opt_key == "split") { for (Model &model : m_models) { size_t num_objects = model.objects.size(); for (size_t i = 0; i < num_objects; ++ i) { ModelObjectPtrs new_objects; model.objects.front()->split(&new_objects); model.delete_object(size_t(0)); } } } else if (opt_key == "repair") { // Models are repaired by default. //for (auto &model : m_models) // model.repair(); } else { boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl; return 1; } } // All transforms have been dealt with. Now ensure that the objects are on bed. // (Unless the user said otherwise.) if (m_config.opt_bool("ensure_on_bed")) for (auto &model : m_models) for (auto &o : model.objects) o->ensure_on_bed(); // loop through action options for (auto const &opt_key : m_actions) { if (opt_key == "help") { this->print_help(); } else if (opt_key == "help_fff") { this->print_help(true, ptFFF); } else if (opt_key == "help_sla") { this->print_help(true, ptSLA); } else if (opt_key == "save") { //FIXME check for mixing the FFF / SLA parameters. // or better save fff_print_config vs. sla_print_config m_print_config.save(m_config.opt_string("save")); } else if (opt_key == "info") { // --info works on unrepaired model for (Model &model : m_models) { model.add_default_instances(); model.print_info(); } } else if (opt_key == "export_stl") { for (auto &model : m_models) model.add_default_instances(); if (! this->export_models(IO::STL)) return 1; } else if (opt_key == "export_obj") { for (auto &model : m_models) model.add_default_instances(); if (! this->export_models(IO::OBJ)) return 1; } else if (opt_key == "export_amf") { if (! this->export_models(IO::AMF)) return 1; } else if (opt_key == "export_3mf") { if (! this->export_models(IO::TMF)) return 1; } else if (opt_key == "export_gcode" || opt_key == "export_sla" || opt_key == "slice") { if (opt_key == "export_gcode" && printer_technology == ptSLA) { boost::nowide::cerr << "error: cannot export G-code for an FFF configuration" << std::endl; return 1; } else if (opt_key == "export_sla" && printer_technology == ptFFF) { boost::nowide::cerr << "error: cannot export SLA slices for a SLA configuration" << std::endl; return 1; } // Make a copy of the model if the current action is not the last action, as the model may be // modified by the centering and such. Model model_copy; bool make_copy = &opt_key != &m_actions.back(); for (Model &model_in : m_models) { if (make_copy) model_copy = model_in; Model &model = make_copy ? model_copy : model_in; // If all objects have defined instances, their relative positions will be // honored when printing (they will be only centered, unless --dont-arrange // is supplied); if any object has no instances, it will get a default one // and all instances will be rearranged (unless --dont-arrange is supplied). std::string outfile = m_config.opt_string("output"); Print fff_print; SLAPrint sla_print; sla_print.set_status_callback( [](const PrintBase::SlicingStatus& s) { if(s.percent >= 0) // FIXME: is this sufficient? printf("%3d%s %s\n", s.percent, "% =>", s.text.c_str()); }); PrintBase *print = (printer_technology == ptFFF) ? static_cast(&fff_print) : static_cast(&sla_print); if (! m_config.opt_bool("dont_arrange")) { if (user_center_specified) { Vec2d c = m_config.option("center")->value; arrange_objects(model, InfiniteBed{scaled(c)}, arrange_cfg); } else arrange_objects(model, bed, arrange_cfg); } if (printer_technology == ptFFF) { for (auto* mo : model.objects) fff_print.auto_assign_extruders(mo); } print->apply(model, m_print_config); std::string err = print->validate(); if (! err.empty()) { boost::nowide::cerr << err << std::endl; return 1; } if (print->empty()) boost::nowide::cout << "Nothing to print for " << outfile << " . Either the print is empty or no object is fully inside the print volume." << std::endl; else try { std::string outfile_final; print->process(); if (printer_technology == ptFFF) { // The outfile is processed by a PlaceholderParser. outfile = fff_print.export_gcode(outfile, nullptr, nullptr); outfile_final = fff_print.print_statistics().finalize_output_path(outfile); } else { outfile = sla_print.output_filepath(outfile); // We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata outfile_final = sla_print.print_statistics().finalize_output_path(outfile); sla_print.export_print(outfile_final); } if (outfile != outfile_final) { if (Slic3r::rename_file(outfile, outfile_final)) { boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; return 1; } outfile = outfile_final; } // Run the post-processing scripts if defined. run_post_process_scripts(outfile, fff_print.full_print_config()); boost::nowide::cout << "Slicing result exported to " << outfile << std::endl; } catch (const std::exception &ex) { boost::nowide::cerr << ex.what() << std::endl; return 1; } /* print.center = ! m_config.has("center") && ! m_config.has("align_xy") && ! m_config.opt_bool("dont_arrange"); print.set_model(model); // start chronometer typedef std::chrono::high_resolution_clock clock_; typedef std::chrono::duration > second_; std::chrono::time_point t0{ clock_::now() }; const std::string outfile = this->output_filepath(model, IO::Gcode); try { print.export_gcode(outfile); } catch (std::runtime_error &e) { boost::nowide::cerr << e.what() << std::endl; return 1; } boost::nowide::cout << "G-code exported to " << outfile << std::endl; // output some statistics double duration { std::chrono::duration_cast(clock_::now() - t0).count() }; boost::nowide::cout << std::fixed << std::setprecision(0) << "Done. Process took " << (duration/60) << " minutes and " << std::setprecision(3) << std::fmod(duration, 60.0) << " seconds." << std::endl << std::setprecision(2) << "Filament required: " << print.total_used_filament() << "mm" << " (" << print.total_extruded_volume()/1000 << "cm3)" << std::endl; */ } } else { boost::nowide::cerr << "error: option not supported yet: " << opt_key << std::endl; return 1; } } if (start_gui) { #ifdef SLIC3R_GUI #if !defined(_WIN32) && !defined(__APPLE__) // likely some linux / unix system const char *display = boost::nowide::getenv("DISPLAY"); // const char *wayland_display = boost::nowide::getenv("WAYLAND_DISPLAY"); //if (! ((display && *display) || (wayland_display && *wayland_display))) { if (! (display && *display)) { // DISPLAY not set. boost::nowide::cerr << "DISPLAY not set, GUI mode not available." << std::endl << std::endl; this->print_help(false); // Indicate an error. return 1; } #endif // some linux / unix system Slic3r::GUI::GUI_InitParams params; params.argc = argc; params.argv = argv; params.load_configs = load_configs; params.extra_config = std::move(m_extra_config); params.input_files = std::move(m_input_files); params.start_as_gcodeviewer = start_as_gcodeviewer; return Slic3r::GUI::GUI_Run(params); #else // SLIC3R_GUI // No GUI support. Just print out a help. this->print_help(false); // If started without a parameter, consider it to be OK, otherwise report an error code (no action etc). return (argc == 0) ? 0 : 1; #endif // SLIC3R_GUI } return 0; } bool CLI::setup(int argc, char **argv) { { Slic3r::set_logging_level(1); const char *loglevel = boost::nowide::getenv("SLIC3R_LOGLEVEL"); if (loglevel != nullptr) { if (loglevel[0] >= '0' && loglevel[0] <= '9' && loglevel[1] == 0) set_logging_level(loglevel[0] - '0'); else boost::nowide::cerr << "Invalid SLIC3R_LOGLEVEL environment variable: " << loglevel << std::endl; } } // Detect the operating system flavor after SLIC3R_LOGLEVEL is set. detect_platform(); #ifdef WIN32 // Notify user that a blacklisted DLL was injected into PrusaSlicer process (for example Nahimic, see GH #5573). // We hope that if a DLL is being injected into a PrusaSlicer process, it happens at the very start of the application, // thus we shall detect them now. if (BlacklistedLibraryCheck::get_instance().perform_check()) { std::wstring text = L"Following DLLs have been injected into the PrusaSlicer process:\n\n"; text += BlacklistedLibraryCheck::get_instance().get_blacklisted_string(); text += L"\n\n" L"PrusaSlicer is known to not run correctly with these DLLs injected. " L"We suggest stopping or uninstalling these services if you experience " L"crashes or unexpected behaviour while using PrusaSlicer.\n" L"For example, ASUS Sonic Studio injects a Nahimic driver, which makes PrusaSlicer " L"to crash on a secondary monitor, see PrusaSlicer github issue #5573"; MessageBoxW(NULL, text.c_str(), L"Warning"/*L"Incopatible library found"*/, MB_OK); } #endif // See Invoking prusa-slicer from $PATH environment variable crashes #5542 // boost::filesystem::path path_to_binary = boost::filesystem::system_complete(argv[0]); boost::filesystem::path path_to_binary = boost::dll::program_location(); // Path from the Slic3r binary to its resources. #ifdef __APPLE__ // The application is packed in the .dmg archive as 'Slic3r.app/Contents/MacOS/Slic3r' // The resources are packed to 'Slic3r.app/Contents/Resources' boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path() / "../Resources"; #elif defined _WIN32 // The application is packed in the .zip archive in the root, // The resources are packed to 'resources' // Path from Slic3r binary to resources: boost::filesystem::path path_resources = path_to_binary.parent_path() / "resources"; #elif defined SLIC3R_FHS // The application is packaged according to the Linux Filesystem Hierarchy Standard // Resources are set to the 'Architecture-independent (shared) data', typically /usr/share or /usr/local/share boost::filesystem::path path_resources = SLIC3R_FHS_RESOURCES; #else // The application is packed in the .tar.bz archive (or in AppImage) as 'bin/slic3r', // The resources are packed to 'resources' // Path from Slic3r binary to resources: boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path() / "../resources"; #endif set_resources_dir(path_resources.string()); set_var_dir((path_resources / "icons").string()); set_local_dir((path_resources / "localization").string()); set_sys_shapes_dir((path_resources / "shapes").string()); // Parse all command line options into a DynamicConfig. // If any option is unsupported, print usage and abort immediately. t_config_option_keys opt_order; if (! m_config.read_cli(argc, argv, &m_input_files, &opt_order)) { // Separate error message reported by the CLI parser from the help. boost::nowide::cerr << std::endl; this->print_help(); return false; } // Parse actions and transform options. for (auto const &opt_key : opt_order) { if (cli_actions_config_def.has(opt_key)) m_actions.emplace_back(opt_key); else if (cli_transform_config_def.has(opt_key)) m_transforms.emplace_back(opt_key); } { const ConfigOptionInt *opt_loglevel = m_config.opt("loglevel"); if (opt_loglevel != 0) set_logging_level(opt_loglevel->value); } //FIXME Validating at this stage most likely does not make sense, as the config is not fully initialized yet. std::string validity = m_config.validate(); // Initialize with defaults. for (const t_optiondef_map *options : { &cli_actions_config_def.options, &cli_transform_config_def.options, &cli_misc_config_def.options }) for (const t_optiondef_map::value_type &optdef : *options) m_config.option(optdef.first, true); set_data_dir(m_config.opt_string("datadir")); //FIXME Validating at this stage most likely does not make sense, as the config is not fully initialized yet. if (!validity.empty()) { boost::nowide::cerr << "error: " << validity << std::endl; return false; } return true; } void CLI::print_help(bool include_print_options, PrinterTechnology printer_technology) const { boost::nowide::cout << SLIC3R_BUILD_ID << " " << "based on Slic3r" #ifdef SLIC3R_GUI << " (with GUI support)" #else /* SLIC3R_GUI */ << " (without GUI support)" #endif /* SLIC3R_GUI */ << std::endl << "https://github.com/prusa3d/PrusaSlicer" << std::endl << std::endl << "Usage: prusa-slicer [ ACTIONS ] [ TRANSFORM ] [ OPTIONS ] [ file.stl ... ]" << std::endl << std::endl << "Actions:" << std::endl; cli_actions_config_def.print_cli_help(boost::nowide::cout, false); boost::nowide::cout << std::endl << "Transform options:" << std::endl; cli_transform_config_def.print_cli_help(boost::nowide::cout, false); boost::nowide::cout << std::endl << "Other options:" << std::endl; cli_misc_config_def.print_cli_help(boost::nowide::cout, false); boost::nowide::cout << std::endl << "Print options are processed in the following order:" << std::endl << "\t1) Config keys from the command line, for example --fill-pattern=stars" << std::endl << "\t (highest priority, overwrites everything below)" << std::endl << "\t2) Config files loaded with --load" << std::endl << "\t3) Config values loaded from amf or 3mf files" << std::endl; if (include_print_options) { boost::nowide::cout << std::endl; print_config_def.print_cli_help(boost::nowide::cout, true, [printer_technology](const ConfigOptionDef &def) { return printer_technology == ptAny || def.printer_technology == ptAny || printer_technology == def.printer_technology; }); } else { boost::nowide::cout << std::endl << "Run --help-fff / --help-sla to see the full listing of print options." << std::endl; } } bool CLI::export_models(IO::ExportFormat format) { for (Model &model : m_models) { const std::string path = this->output_filepath(model, format); bool success = false; switch (format) { case IO::AMF: success = Slic3r::store_amf(path.c_str(), &model, nullptr, false); break; case IO::OBJ: success = Slic3r::store_obj(path.c_str(), &model); break; case IO::STL: success = Slic3r::store_stl(path.c_str(), &model, true); break; case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr, false); break; default: assert(false); break; } if (success) std::cout << "File exported to " << path << std::endl; else { std::cerr << "File export to " << path << " failed" << std::endl; return false; } } return true; } std::string CLI::output_filepath(const Model &model, IO::ExportFormat format) const { std::string ext; switch (format) { case IO::AMF: ext = ".zip.amf"; break; case IO::OBJ: ext = ".obj"; break; case IO::STL: ext = ".stl"; break; case IO::TMF: ext = ".3mf"; break; default: assert(false); break; }; auto proposed_path = boost::filesystem::path(model.propose_export_file_name_and_path(ext)); // use --output when available std::string cmdline_param = m_config.opt_string("output"); if (! cmdline_param.empty()) { // if we were supplied a directory, use it and append our automatically generated filename boost::filesystem::path cmdline_path(cmdline_param); if (boost::filesystem::is_directory(cmdline_path)) proposed_path = cmdline_path / proposed_path.filename(); else proposed_path = cmdline_path; } return proposed_path.string(); } // __has_feature() is used later for Clang, this is for compatibility with other compilers (such as GCC and MSVC) #ifndef __has_feature # define __has_feature(x) 0 #endif #if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) extern "C" { // Based on https://github.com/google/skia/blob/main/tools/LsanSuppressions.cpp const char *__lsan_default_suppressions() { return "leak:libfontconfig\n" // FontConfig looks like it leaks, but it doesn't. "leak:libfreetype\n" // Unsure, appeared upgrading Debian 9->10. "leak:libGLX_nvidia.so\n" // For NVidia driver. "leak:libnvidia-glcore.so\n" // For NVidia driver. "leak:libnvidia-tls.so\n" // For NVidia driver. "leak:terminator_CreateDevice\n" // For Intel Vulkan drivers. "leak:swrast_dri.so\n" // For Mesa 3D software driver. "leak:amdgpu_dri.so\n" // For AMD driver. "leak:libdrm_amdgpu.so\n" // For AMD driver. "leak:libdbus-1.so\n" // For D-Bus library. Unsure if it is a leak or not. ; } } #endif #if defined(SLIC3R_UBSAN) extern "C" { // Enable printing stacktrace by default. It can be disabled by running PrusaSlicer with "UBSAN_OPTIONS=print_stacktrace=0". const char *__ubsan_default_options() { return "print_stacktrace=1"; } } #endif #if defined(_MSC_VER) || defined(__MINGW32__) extern "C" { __declspec(dllexport) int __stdcall slic3r_main(int argc, wchar_t **argv) { // Convert wchar_t arguments to UTF8. std::vector argv_narrow; std::vector argv_ptrs(argc + 1, nullptr); for (size_t i = 0; i < argc; ++ i) argv_narrow.emplace_back(boost::nowide::narrow(argv[i])); for (size_t i = 0; i < argc; ++ i) argv_ptrs[i] = argv_narrow[i].data(); // Call the UTF8 main. return CLI().run(argc, argv_ptrs.data()); } } #else /* _MSC_VER */ int main(int argc, char **argv) { return CLI().run(argc, argv); } #endif /* _MSC_VER */