#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 "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/Model.hpp" #include "libslic3r/ModelArrange.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/AppConfig.hpp" #include "PrusaSlicer.hpp" #ifdef SLIC3R_GUI #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/3DScene.hpp" #include "slic3r/GUI/InstanceCheck.hpp" #include "slic3r/GUI/MainFrame.hpp" #include "slic3r/GUI/Plater.hpp" #endif /* SLIC3R_GUI */ using namespace Slic3r; int CLI::run(int argc, char **argv) { #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(); PrinterTechnology printer_technology = Slic3r::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; // 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; try { config.load(file); } catch (std::exception &ex) { boost::nowide::cerr << "Error while reading config file: " << ex.what() << std::endl; return 1; } config.normalize(); PrinterTechnology other_printer_technology = Slic3r::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); } #if ENABLE_GCODE_VIEWER // 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; } } #endif // ENABLE_GCODE_VIEWER // Read input file(s) if any. #if ENABLE_GCODE_VIEWER if (!start_as_gcodeviewer) { #endif // ENABLE_GCODE_VIEWER 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; model = Model::read_from_file(file, &config, true); PrinterTechnology other_printer_technology = Slic3r::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; } // 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); } #if ENABLE_GCODE_VIEWER } #endif // ENABLE_GCODE_VIEWER // 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(); // 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 if (printer_technology == ptSLA) { // The default value has to be different from the one in fff mode. sla_print_config.printer_technology.value = 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: " << 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 == "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"), true, true, true); #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(); TriangleMeshPtrs 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) { model.objects.front()->split(nullptr); 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; } } // 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; SL1Archive sla_archive(sla_print.printer_config()); sla_print.set_printer(&sla_archive); 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. #if ENABLE_GCODE_VIEWER outfile = fff_print.export_gcode(outfile, nullptr, nullptr); #else outfile = fff_print.export_gcode(outfile, nullptr); #endif // ENABLE_GCODE_VIEWER 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_archive.export_print(outfile_final, sla_print); } if (outfile != outfile_final && Slic3r::rename_file(outfile, outfile_final)) { boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; return 1; } 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; */ } #if !ENABLE_GCODE_VIEWER } else if (opt_key == "gcodeviewer") { start_gui = true; start_as_gcodeviewer = true; #endif // !ENABLE_GCODE_VIEWER } else { boost::nowide::cerr << "error: option not supported yet: " << opt_key << std::endl; return 1; } } if (start_gui) { #ifdef SLIC3R_GUI // #ifdef USE_WX #if ENABLE_GCODE_VIEWER GUI::GUI_App* gui = new GUI::GUI_App(start_as_gcodeviewer ? GUI::GUI_App::EAppMode::GCodeViewer : GUI::GUI_App::EAppMode::Editor); #else GUI::GUI_App *gui = new GUI::GUI_App(); #endif // ENABLE_GCODE_VIEWER bool gui_single_instance_setting = gui->app_config->get("single_instance") == "1"; if (Slic3r::instance_check(argc, argv, gui_single_instance_setting)) { //TODO: do we have delete gui and other stuff? return -1; } // gui->autosave = m_config.opt_string("autosave"); GUI::GUI_App::SetInstance(gui); #if ENABLE_GCODE_VIEWER gui->CallAfter([gui, this, &load_configs, start_as_gcodeviewer] { #else gui->CallAfter([gui, this, &load_configs] { #endif // ENABLE_GCODE_VIEWER if (!gui->initialized()) { return; } #if ENABLE_GCODE_VIEWER if (start_as_gcodeviewer) { if (!m_input_files.empty()) gui->plater()->load_gcode(wxString::FromUTF8(m_input_files[0])); } else { #endif // ENABLE_GCODE_VIEWER_AS #if 0 // Load the cummulative config over the currently active profiles. //FIXME if multiple configs are loaded, only the last one will have an effect. // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). // As of now only the full configs are supported here. if (!m_print_config.empty()) gui->mainframe->load_config(m_print_config); #endif if (!load_configs.empty()) // Load the last config to give it a name at the UI. The name of the preset may be later // changed by loading an AMF or 3MF. //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. gui->mainframe->load_config_file(load_configs.back()); // If loading a 3MF file, the config is loaded from the last one. if (!m_input_files.empty()) gui->plater()->load_files(m_input_files, true, true); if (!m_extra_config.empty()) gui->mainframe->load_config(m_extra_config); #if ENABLE_GCODE_VIEWER } #endif // ENABLE_GCODE_VIEWER }); int result = wxEntry(argc, argv); return result; #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; } } boost::filesystem::path path_to_binary = boost::filesystem::system_complete(argv[0]); // 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 = 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 = 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()); // 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); } 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 std::pair &optdef : *options) m_config.option(optdef.first, true); set_data_dir(m_config.opt_string("datadir")); 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(); } #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 */