#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 #if defined(__linux__) || defined(__LINUX__) #include #include #include //add json logic #include "nlohmann/json.hpp" using namespace nlohmann; #endif #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 "libslic3r/Orient.hpp" #include "BambuStudio.hpp" //BBS: add exception handler for win32 #include #ifdef WIN32 #include "BaseException.h" #endif #include "slic3r/GUI/PartPlate.hpp" #include "slic3r/GUI/BitmapCache.hpp" #include "slic3r/GUI/OpenGLManager.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/Camera.hpp" #include #ifdef SLIC3R_GUI #include "slic3r/GUI/GUI_Init.hpp" #endif /* SLIC3R_GUI */ using namespace Slic3r; /*typedef struct _error_message{ int code; std::string message; }error_message;*/ #define CLI_SUCCESS 0 #define CLI_ENVIRONMENT_ERROR -1 #define CLI_INVALID_PARAMS -2 #define CLI_FILE_NOTFOUND -3 #define CLI_FILELIST_INVALID_ORDER -4 #define CLI_CONFIG_FILE_ERROR -5 #define CLI_DATA_FILE_ERROR -6 #define CLI_INVALID_PRINTER_TECH -7 #define CLI_UNSUPPORTED_OPERATION -8 #define CLI_COPY_OBJECTS_ERROR -9 #define CLI_SCALE_TO_FIT_ERROR -10 #define CLI_EXPORT_STL_ERROR -11 #define CLI_EXPORT_OBJ_ERROR -12 #define CLI_EXPORT_3MF_ERROR -13 #define CLI_NO_SUITABLE_OBJECTS -50 #define CLI_VALIDATE_ERROR -51 #define CLI_OBJECTS_PARTLY_INSIDE -52 #define CLI_SLICING_ERROR -100 std::map cli_errors = { {CLI_SUCCESS, "Success"}, {CLI_ENVIRONMENT_ERROR, "Environment setup failed"}, {CLI_INVALID_PARAMS, "Input param invalid"}, {CLI_FILE_NOTFOUND, "Input file not found"}, {CLI_FILELIST_INVALID_ORDER, "File list order invalid(please make sure 3mf in the first place)"}, {CLI_CONFIG_FILE_ERROR, "Invalid config file, could not be parsed"}, {CLI_DATA_FILE_ERROR, "Invalid model file, could not be loaded"}, {CLI_INVALID_PRINTER_TECH, "Invalid printer technoledge"}, {CLI_UNSUPPORTED_OPERATION, "Unsupported operation"}, {CLI_COPY_OBJECTS_ERROR, "Copy objects error"}, {CLI_SCALE_TO_FIT_ERROR, "Scale to fit error"}, {CLI_EXPORT_STL_ERROR, "Export stl error"}, {CLI_EXPORT_OBJ_ERROR, "Export obj error"}, {CLI_EXPORT_3MF_ERROR, "Export 3mf error"}, {CLI_NO_SUITABLE_OBJECTS, "Found no objects in print volume to slice"}, {CLI_VALIDATE_ERROR, "Validate print error"}, {CLI_OBJECTS_PARTLY_INSIDE, "Objects partly inside"}, {CLI_SLICING_ERROR, "Slice error"} }; #if defined(__linux__) || defined(__LINUX__) #define PIPE_BUFFER_SIZE 512 typedef struct _cli_callback_mgr { int m_plate_count {0}; int m_plate_index {0}; int m_progress { 0 }; int m_total_progress { 0 }; std::string m_message; int m_warning_step; bool m_exit {false}; bool m_data_ready {false}; bool m_started {false}; boost::thread m_thread; // Mutex and condition variable to synchronize m_thread with the UI thread. std::mutex m_mutex; std::condition_variable m_condition; int m_pipe_fd{-1}; bool is_started() { bool result; std::unique_lock lck(m_mutex); result = m_started; lck.unlock(); return result; } void set_plate_info(int index, int count) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": index="<= 0) j["warning"] = m_message; else j["message"] = m_message; std::string notify_message = j.dump(); //notify_message = "Plate "+ std::to_string(m_plate_index) + "/" +std::to_string(m_plate_count)+ ": Percent " + std::to_string(m_progress) + ": "+m_message; char pipe_message[PIPE_BUFFER_SIZE] = {0}; sprintf(pipe_message, "%s\n", notify_message.c_str()); int ret = write(m_pipe_fd, pipe_message, strlen(pipe_message)); BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ": write returns "< lck(m_mutex); m_started = true; m_data_ready = false; lck.unlock(); m_condition.notify_one(); BOOST_LOG_TRIVIAL(info) << "cli_callback_mgr_t::thread_proc started."; while(1) { lck.lock(); m_condition.wait(lck, [this](){ return m_data_ready || m_exit; }); BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ": wakup."; if (m_data_ready) { notify(); m_data_ready = false; } if (m_exit) { BOOST_LOG_TRIVIAL(info) << "cli_callback_mgr_t::thread_proc will exit."; break; } lck.unlock(); m_condition.notify_one(); } lck.unlock(); BOOST_LOG_TRIVIAL(info) << "cli_callback_mgr_t::thread_proc exit."; } void update(int percent, std::string message, int warning_step) { std::unique_lock lck(m_mutex); if (!m_started) { lck.unlock(); return; } if ((m_progress >= percent)&&(warning_step == -1)) { //already update before lck.unlock(); return; } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": percent="< lck(m_mutex); m_thread = create_thread([this]{ this->thread_proc(); }); lck.unlock(); BOOST_LOG_TRIVIAL(info) << "cli_callback_mgr_t::start successfully."; return true; } void stop() { BOOST_LOG_TRIVIAL(info) << "cli_callback_mgr_t::stop enter."; std::unique_lock lck(m_mutex); if (!m_started) { lck.unlock(); BOOST_LOG_TRIVIAL(info) << "cli_callback_mgr_t::stop not started before, return directly."; return; } m_exit = true; lck.unlock(); m_condition.notify_one(); // Wait until the worker thread exits. m_thread.join(); if (m_pipe_fd > 0) { close(m_pipe_fd); m_pipe_fd = -1; } BOOST_LOG_TRIVIAL(info) << "cli_callback_mgr_t::stop successfully."; } }cli_callback_mgr_t; cli_callback_mgr_t g_cli_callback_mgr; void cli_status_callback(const PrintBase::SlicingStatus& slicing_status) { g_cli_callback_mgr.update(slicing_status.percent, slicing_status.text, slicing_status.warning_step); return; } #endif static PrinterTechnology get_printer_technology(const DynamicConfig &config) { const ConfigOptionEnum *opt = config.option>("printer_technology"); return (opt == nullptr) ? ptUnknown : opt->value; } //BBS: add flush and exit #if defined(__linux__) || defined(__LINUX__) #define flush_and_exit(ret) { boost::nowide::cout << __FUNCTION__ << " found error, return "<setup(debug_argc, debug_argv))*/ if (!this->setup(argc, argv)) { boost::nowide::cerr << "setup params error" << std::endl; return CLI_INVALID_PARAMS; } BOOST_LOG_TRIVIAL(info) << "finished setup params, argc="<< argc << std::endl; std::string temp_path = wxFileName::GetTempDir().utf8_str().data(); set_temporary_dir(temp_path); 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(); //BBS: remove GCodeViewer as seperate APP logic /*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_settings", true)->values; //BBS: always use ForwardCompatibilitySubstitutionRule::Enable //const ForwardCompatibilitySubstitutionRule config_substitution_rule = m_config.option>("config_compatibility", true)->value; const ForwardCompatibilitySubstitutionRule config_substitution_rule = ForwardCompatibilitySubstitutionRule::Enable; const std::vector &load_filaments = m_config.option("load_filaments", true)->values; if (start_gui) { BOOST_LOG_TRIVIAL(info) << "no action, start gui directly" << std::endl; ::Label::initSysFont(); #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); std::vector gcode_files; std::vector non_gcode_files; for (const auto& filename : m_input_files) { if (is_gcode_file(filename)) gcode_files.emplace_back(filename); else { non_gcode_files.emplace_back(filename); } } if (non_gcode_files.empty() && !gcode_files.empty()) { params.input_gcode = true; params.input_files = std::move(gcode_files); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", gcode only, gcode_files size = "<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 } BOOST_LOG_TRIVIAL(info) << "before load settings, file count="<< load_configs.size() << std::endl; // load config files supplied via --load for (auto const &file : load_configs) { if (! boost::filesystem::exists(file)) { boost::nowide::cerr << "can not find setting file: " << file << std::endl; flush_and_exit(CLI_FILE_NOTFOUND); } DynamicPrintConfig config; ConfigSubstitutions config_substitutions; try { BOOST_LOG_TRIVIAL(info) << "load setting file "<< file << ", with rule "<< config_substitution_rule << std::endl; std::map key_values; std::string reason; config_substitutions = config.load_from_json(file, config_substitution_rule, key_values, reason); if (!reason.empty()) { BOOST_LOG_TRIVIAL(error) << "Can not load config from file "<serialize() << std::endl; } catch (std::exception &ex) { boost::nowide::cerr << "Loading setting file \"" << file << "\" failed: " << ex.what() << std::endl; flush_and_exit(CLI_CONFIG_FILE_ERROR); } if (! config_substitutions.empty()) { BOOST_LOG_TRIVIAL(info) << "Found legacy configuration values, substituted when loading " << file << ":\n"; for (const ConfigSubstitution &subst : config_substitutions) BOOST_LOG_TRIVIAL(info) << "\tkey = \"" << subst.opt_def->opt_key << "\"\t old_value = \"" << subst.old_value << "\tnew_value = \"" << subst.new_value->serialize() << "\"\n"; } else { BOOST_LOG_TRIVIAL(info) << "no substitutions performed from file " << file << "\n"; } config.normalize_fdm(); PrinterTechnology other_printer_technology = get_printer_technology(config); if (printer_technology == ptUnknown) { printer_technology = other_printer_technology; } if ((printer_technology != other_printer_technology)&&(other_printer_technology != ptUnknown)){ boost::nowide::cerr << "invalid printer_technology " < key_values; try { BOOST_LOG_TRIVIAL(info) << "load filament file "<< file << ", with rule "<< config_substitution_rule << std::endl; std::string reason; config_substitutions = config.load_from_json(file, config_substitution_rule, key_values, reason); if (!reason.empty()) { BOOST_LOG_TRIVIAL(error) << "Can not load filament config from file "<serialize() << std::endl; } catch (std::exception &ex) { boost::nowide::cerr << "Loading filament file \"" << file << "\" failed: " << ex.what() << std::endl; flush_and_exit(CLI_CONFIG_FILE_ERROR); } if (! config_substitutions.empty()) { BOOST_LOG_TRIVIAL(info) << "Found legacy configuration values, substituted when loading " << file << ":\n"; for (const ConfigSubstitution &subst : config_substitutions) BOOST_LOG_TRIVIAL(info) << "\tkey = \"" << subst.opt_def->opt_key << "\"\t old_value = \"" << subst.old_value << "\tnew_value = \"" << subst.new_value->serialize() << "\"\n"; } else { BOOST_LOG_TRIVIAL(info) << "no substitutions performed from file " << file << "\n"; } config.normalize_fdm(); PrinterTechnology other_printer_technology = get_printer_technology(config); if (printer_technology == ptUnknown) { printer_technology = other_printer_technology; } if ((printer_technology != other_printer_technology) && (other_printer_technology != ptUnknown)) { boost::nowide::cerr << "invalid printer_technology " < (m_print_config.option("filament_settings_id", true)); ConfigOptionStrings *opt_filament_settings_src = static_cast(config.option("filament_settings_id", false)); if (opt_filament_settings_src) opt_filament_settings->set_at(opt_filament_settings_src, index, 0); else { std::string name = file; name.erase(name.size() - 5); ConfigOptionString option(name); opt_filament_settings->set_at(&option, index, 0); } std::string filament_id; ConfigOptionStrings *opt_filament_ids = static_cast (m_print_config.option("filament_ids", true)); auto filament_id_iter = key_values.find(BBL_JSON_KEY_FILAMENT_ID); if (filament_id_iter != key_values.end()) filament_id = filament_id_iter->second; ConfigOptionString* filament_id_setting = new ConfigOptionString(filament_id); if (opt_filament_ids->size() < filament_count) opt_filament_ids->resize(filament_count, filament_id_setting); opt_filament_ids->set_at(filament_id_setting, index, 0); //parse the filament value to index th //loop through options and apply them for (const t_config_option_key &opt_key : config.keys()) { // Create a new option with default value for the key. // If the key is not in the parameter definition, or this ConfigBase is a static type and it does not support the parameter, // an exception is thrown if not ignore_nonexistent. const ConfigOption *source_opt = config.option(opt_key); if (source_opt == nullptr) { // The key was not found in the source config, therefore it will not be initialized! boost::nowide::cerr << "can not found option " <is_scalar()) { if (opt_key == "compatible_printers_condition") { ConfigOption *opt = m_print_config.option("compatible_machine_expression_group", true); ConfigOptionStrings* opt_vec_dst = static_cast(opt); if (opt_vec_dst->size() == 0) opt_vec_dst->resize(1, new ConfigOptionString()); opt_vec_dst->set_at(source_opt, index+1, 0); } else if (opt_key == "compatible_prints_condition") { ConfigOption *opt = m_print_config.option("compatible_process_expression_group", true); ConfigOptionStrings* opt_vec_dst = static_cast(opt); if (opt_vec_dst->size() == 0) opt_vec_dst->resize(1, new ConfigOptionString()); opt_vec_dst->set_at(source_opt, index, 0); } else { //skip the scalar values BOOST_LOG_TRIVIAL(info) << "skip scalar option " <def(). // This is only possible if other is of DynamicConfig type. boost::nowide::cerr << "can not create option " <(opt); const ConfigOptionVectorBase* opt_vec_src = static_cast(source_opt); if (opt_key == "bed_temperature" || opt_key == "bed_temperature_initial_layer") { const ConfigOptionInts* bed_temp_opt = dynamic_cast(opt_vec_src); for (size_t type = 0; type < (size_t)BedType::btCount; type++) { if (type < bed_temp_opt->size()) opt_vec_dst->set_at(bed_temp_opt, index * BedType::btCount + type, type); else // BBS FIXME: set bed temperature to 0 for new bed types opt_vec_dst->set_at(new ConfigOptionInt(0), index * BedType::btCount + type, type); } } else if (opt_key == "compatible_prints" || opt_key == "compatible_printers") continue; else { opt_vec_dst->set_at(opt_vec_src, index, 0); } } } } // 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; } }*/ BOOST_LOG_TRIVIAL(info) << "start_gui="<< start_gui << std::endl; //BBS: add plate data related logic PlateDataPtrs plate_data; int arrange_option; bool first_file = true, is_bbl_3mf = false, need_arrange = true; Semver file_version; std::map orients_requirement; std::vector project_presets; // Read input file(s) if any. BOOST_LOG_TRIVIAL(info) << "Will start to read model file now, file count :" << m_input_files.size() << "\n"; /*for (const std::string& file : m_input_files) if (is_gcode_file(file) && boost::filesystem::exists(file)) { start_as_gcodeviewer = true; BOOST_LOG_TRIVIAL(info) << "found a gcode file:" << file << ", will start as gcode viewer\n"; 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; flush_and_exit(CLI_FILE_NOTFOUND); } Model model; //BBS: add plate related logic //bool load_aux = false; BOOST_LOG_TRIVIAL(info) << "read model file:" << file << "\n"; 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? // | LoadStrategy::CheckVersion ? is_bbl_3mf = false; LoadStrategy strategy; if (boost::algorithm::iends_with(file, ".3mf") && first_file) { strategy = LoadStrategy::LoadModel | LoadStrategy::LoadConfig|LoadStrategy::AddDefaultInstances | LoadStrategy::LoadAuxiliary; //load_aux = true; } else strategy = LoadStrategy::LoadModel | LoadStrategy::AddDefaultInstances; // BBS: adjust whebackup //LoadStrategy strategy = LoadStrategy::LoadModel | LoadStrategy::LoadConfig|LoadStrategy::AddDefaultInstances; //if (load_aux) strategy = strategy | LoadStrategy::LoadAuxiliary; model = Model::read_from_file(file, &config, &config_substitutions, strategy, &plate_data, &project_presets, &is_bbl_3mf, &file_version); if (is_bbl_3mf) { if (!first_file) { BOOST_LOG_TRIVIAL(info) << "The BBL 3mf file should be placed at the first position, filename=" << file << "\n"; flush_and_exit(CLI_FILELIST_INVALID_ORDER); } BOOST_LOG_TRIVIAL(info) << "the first file is a 3mf, got plate count:" << plate_data.size() << "\n"; need_arrange = false; for (ModelObject* o : model.objects) { orients_requirement.insert(std::pair(o->id().id, false)); BOOST_LOG_TRIVIAL(info) << "object "<name <<", id :" << o->id().id << ", from bbl 3mf\n"; } /*for (ModelObject *model_object : model.objects) for (ModelInstance *model_instance : model_object->instances) { const Vec3d &instance_offset = model_instance->get_offset(); BOOST_LOG_TRIVIAL(info) << boost::format("instance %1% transform {%2%,%3%,%4%} at %5%:%6%")% model_object->name % instance_offset.x() % instance_offset.y() %instance_offset.z() % __FUNCTION__ % __LINE__<< std::endl; }*/ } else { need_arrange = true; for (ModelObject* o : model.objects) { orients_requirement.insert(std::pair(o->id().id, true)); BOOST_LOG_TRIVIAL(info) << "object "<name <<", id :" << o->id().id << ", from stl or other 3mf\n"; o->ensure_on_bed(); } } first_file = false; PrinterTechnology other_printer_technology = get_printer_technology(config); if (printer_technology == ptUnknown) { printer_technology = other_printer_technology; } if ((printer_technology != other_printer_technology) && (other_printer_technology != ptUnknown)) { boost::nowide::cerr << "invalid printer_technology " <opt_key << "\"\t old_value = \"" << subst.old_value << "\tnew_value = \"" << 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; flush_and_exit(CLI_DATA_FILE_ERROR); } if (model.objects.empty()) { boost::nowide::cerr << "Error: file is empty: " << file << std::endl; continue; } m_models.push_back(std::move(model)); } //} //BBS: set default to ptFFF if (printer_technology == ptUnknown) printer_technology = ptFFF; //BBS: merge these models into one BOOST_LOG_TRIVIAL(info) << "total " << m_models.size() << " models, "< 1) { BOOST_LOG_TRIVIAL(info) << "merge all the models into one\n"; Model m; m.set_backup_path(m_models[0].get_backup_path()); for (auto& model : m_models) for (ModelObject* o : model.objects) { ModelObject* new_object = m.add_object(*o); //BOOST_LOG_TRIVIAL(info) << "object "<name <<", id :" << o->id().id << "\n"; orients_requirement.emplace(new_object->id().id, orients_requirement[o->id().id]); orients_requirement.erase(o->id().id); } m.add_default_instances(); m_models.clear(); m_models.emplace_back(std::move(m)); } // 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(); 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 { boost::nowide::cerr << "invalid printer_technology " << std::endl; flush_and_exit(CLI_INVALID_PRINTER_TECH); /*assert(printer_technology == ptSLA); sla_print_config.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.printable_area.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; flush_and_exit(CLI_INVALID_PRINTER_TECH); } //BBS: partplate list Slic3r::GUI::PartPlateList partplate_list(NULL, m_models.data(), printer_technology); //use Pointfs insteadof Points Pointfs bedfs = m_print_config.opt("printable_area")->values; Pointfs excluse_areas = m_print_config.opt("bed_exclude_area")->values; //update part plate's size double print_height = m_print_config.opt_float("printable_height"); double height_to_lid = m_print_config.opt_float("extruder_clearance_height_to_lid"); double height_to_rod = m_print_config.opt_float("extruder_clearance_height_to_rod"); double plate_stride; if (m_models.size() > 0) { std::string bed_texture; partplate_list.reset_size(bedfs[2].x() - bedfs[0].x(), bedfs[2].y() - bedfs[0].y(), print_height); partplate_list.set_shapes(bedfs, excluse_areas, bed_texture, height_to_lid, height_to_rod); plate_stride = partplate_list.plate_stride_x(); BOOST_LOG_TRIVIAL(info) << "bed size, x="< 0) { partplate_list.load_from_3mf_structure(plate_data); release_PlateData_list(plate_data); } /*for (ModelObject *model_object : m_models[0].objects) for (ModelInstance *model_instance : model_object->instances) { const Vec3d &instance_offset = model_instance->get_offset(); BOOST_LOG_TRIVIAL(info) << boost::format("instance %1% transform {%2%,%3%,%4%} at %5%:%6%")% model_object->name % instance_offset.x() % instance_offset.y() %instance_offset.z() % __FUNCTION__ % __LINE__<< std::endl; }*/ // Loop through transform options. bool user_center_specified = false; Points beds = get_bed_shape(m_print_config); ArrangeParams arrange_cfg; arrange_cfg.min_obj_distance = scaled(min_object_distance(m_print_config)); BOOST_LOG_TRIVIAL(info) << "will start transforms, commands count " << m_transforms.size() << "\n"; for (auto const &opt_key : m_transforms) { BOOST_LOG_TRIVIAL(info) << "process transform " << opt_key << "\n"; if (opt_key == "merge") { //BBS: always merge, do nothing here /*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 == "convert_unit") { for (auto& model : m_models) { if (model.looks_like_saved_in_meters()) { BOOST_LOG_TRIVIAL(info) << "convert from meter to millimeter\n"; model.convert_from_meters(true); } else if (model.looks_like_imperial_units()) { BOOST_LOG_TRIVIAL(info) << "convert from inch to millimeter\n"; model.convert_from_imperial_units(true); } } } else if (opt_key == "orient") { for (auto& model : m_models) for (ModelObject* o : model.objects) { // coconut: always orient instance instead of object for (ModelInstance* mi : o->instances) { orientation::orient(mi); } BOOST_LOG_TRIVIAL(info) << "orient object, name=" << o->name <<",id="<id().id<id().id] = false; } } else if (opt_key == "copy") { 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("copy"); 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), beds, arrange_cfg); } else { arrange_objects(model, beds, arrange_cfg); } } catch (std::exception &ex) { boost::nowide::cerr << "error: " << ex.what() << std::endl; flush_and_exit(CLI_COPY_OBJECTS_ERROR); } } } 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 == "arrange") { //BBS: arrange 0 means disable, 1 means force arrange, others means auto int arrange_option = m_config.option("arrange")->value; if (arrange_option == 0) { need_arrange = false; } else if (arrange_option == 1) { need_arrange = true; } else { //auto arrange, keep the original logic } } 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; flush_and_exit(CLI_SCALE_TO_FIT_ERROR); } 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 ModelObject* object = model.objects.front(); const BoundingBoxf3& box = object->bounding_box(); const float Margin = 20.0; const float max_x = box.size()(0) / 2.0 + Margin; const float min_x = -max_x; const float max_y = box.size()(1) / 2.0 + Margin; const float min_y = -max_y; std::array plane_points; plane_points[0] = { min_x, min_y, 0 }; plane_points[1] = { max_x, min_y, 0 }; plane_points[2] = { max_x, max_y, 0 }; plane_points[3] = { min_x, max_y, 0 }; for (Vec3d& point : plane_points) { point += box.center(); } model.objects.front()->cut(0, plane_points, ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower); #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; flush_and_exit(CLI_UNSUPPORTED_OPERATION); } } BOOST_LOG_TRIVIAL(info) << "finished model pre-process commands\n"; //BBS: add orient and arrange logic here for (auto& model : m_models) { for (ModelObject* o : model.objects) { if (orients_requirement[o->id().id]) { BOOST_LOG_TRIVIAL(info) << "Before process command, Orient object, name=" << o->name <<",id="<id().id<id().id<instances.size(); ++inst_idx) { ModelInstance* minst = mo->instances[inst_idx]; ArrangePolygon ap = get_instance_arrange_poly(minst, m_print_config); //preprocess by partplate list //remove the locked plate's instances, neither in selected, nor in un-selected bool locked = partplate_list.preprocess_arrange_polygon(oidx, inst_idx, ap, true); if (!locked) { ap.itemid = selected.size(); if (minst->printable) selected.emplace_back(ap); else unprintable.emplace_back(ap); } else { //skip this object due to be locked in plate ap.itemid = locked_aps.size(); locked_aps.emplace_back(ap); boost::nowide::cout <<__FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, instance_id %2%") % oidx % inst_idx; } } } //add the virtual object into unselect list if has partplate_list.preprocess_exclude_areas(unselected); //Step-2:prepare the arrange params arrange_cfg.allow_rotations = true; arrange_cfg.min_obj_distance = scaled(6.0); //BBS: add specific params arrange_cfg.is_seq_print = false; arrange_cfg.bed_shrink_x = 0; arrange_cfg.bed_shrink_y = 0; double skirt_distance = m_print_config.opt_float("skirt_distance"); double brim_width = m_print_config.opt_float("brim_width"); arrange_cfg.brim_skirt_distance = skirt_distance + brim_width; BOOST_LOG_TRIVIAL(info) << boost::format("Arrange Params: brim_skirt_distance=%1%, min_obj_distance=%2%, is_seq_print=%3%\n") % arrange_cfg.brim_skirt_distance % arrange_cfg.min_obj_distance % arrange_cfg.is_seq_print; // Note: skirt_distance is now defined between outermost brim and skirt, not the object and skirt. // So we can't do max but do adding instead. arrange_cfg.bed_shrink_x += arrange_cfg.brim_skirt_distance; arrange_cfg.bed_shrink_y += arrange_cfg.brim_skirt_distance; // shrink bed beds[0] += Point(scaled(arrange_cfg.bed_shrink_x), scaled(arrange_cfg.bed_shrink_y)); beds[1] += Point(-scaled(arrange_cfg.bed_shrink_x), scaled(arrange_cfg.bed_shrink_y)); beds[2] += Point(-scaled(arrange_cfg.bed_shrink_x), -scaled(arrange_cfg.bed_shrink_y)); beds[3] += Point(scaled(arrange_cfg.bed_shrink_x), -scaled(arrange_cfg.bed_shrink_y)); // do not inflate brim_width. Objects are allowed to have overlapped brim. std::for_each(selected.begin(), selected.end(), [&](auto& ap) {ap.inflation = arrange_cfg.min_obj_distance / 2; }); { BOOST_LOG_TRIVIAL(info) << "items selected before arranging: "; for (auto selected : selected) BOOST_LOG_TRIVIAL(info) << selected.name << ", extruder: " << selected.extrude_ids.back() << ", bed: " << selected.bed_idx << ", trans: " << selected.translation.transpose(); } arrange_cfg.progressind= [](unsigned st, std::string str = "") { boost::nowide::cout << "st=" << st << ", " << str << std::endl; }; //Step-3:do the arrange arrangement::arrange(selected, unselected, beds, arrange_cfg); arrangement::arrange(unprintable, {}, beds, arrange_cfg); //Step-4:postprocess by partplate list&&apply the result int bed_idx_max = 0; //clear all the relations before apply the arrangement results partplate_list.clear(); // Apply the arrange result to all selected objects for (ArrangePolygon &ap : selected) { //BBS: partplate postprocess partplate_list.postprocess_bed_index_for_selected(ap); bed_idx_max = std::max(ap.bed_idx, bed_idx_max); boost::nowide::cout<< "after arrange: name=" << ap.name << boost::format(",bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale(ap.translation(X)) % unscale(ap.translation(Y)) << "\n"; } for (ArrangePolygon &ap : locked_aps) { bed_idx_max = std::max(ap.bed_idx, bed_idx_max); partplate_list.postprocess_arrange_polygon(ap, false); ap.apply(); } // Apply the arrange result to all selected objects for (ArrangePolygon &ap : selected) { //BBS: partplate postprocess partplate_list.postprocess_arrange_polygon(ap, true); ap.apply(); } // Move the unprintable items to the last virtual bed. for (ArrangePolygon &ap : unprintable) { ap.bed_idx += bed_idx_max + 1; partplate_list.postprocess_arrange_polygon(ap, true); ap.apply(); } //BBS: reload all objects due to arrange partplate_list.rebuild_plates_after_arrangement(); } } // All transforms have been dealt with. Now ensure that the objects are on bed. // (Unless the user said otherwise.) //BBS: current only support models on bed //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 bool export_to_3mf = false; int plate_to_slice = 0; std::string export_3mf_file; std::string outfile_dir = m_config.opt_string("outputdir"); std::vector calibration_thumbnails; 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 == "pipe") { #if defined(__linux__) || defined(__LINUX__) std::string pipe_name = m_config.option("pipe")->value; g_cli_callback_mgr.start(pipe_name); #endif } else if (opt_key == "export_settings") { //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")); m_print_config.save_to_json(m_config.opt_string(opt_key), std::string("project_settings"), std::string("project"), std::string(SLIC3R_VERSION)); } 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)) flush_and_exit(CLI_EXPORT_STL_ERROR); } else if (opt_key == "expor1t_obj") { for (auto &model : m_models) model.add_default_instances(); if (! this->export_models(IO::OBJ)) flush_and_exit(CLI_EXPORT_OBJ_ERROR); }/* else if (opt_key == "export_amf") { if (! this->export_models(IO::AMF)) return 1; } */else if (opt_key == "export_3mf") { export_to_3mf = true; export_3mf_file = m_config.opt_string(opt_key); //} else if (opt_key == "export_gcode" || opt_key == "export_sla" || opt_key == "slice") { } else if (opt_key == "slice") { //BBS: slice 0 means all plates, i means plate i; plate_to_slice = m_config.option("slice")->value; /*if (opt_key == "export_gcode" && printer_technology == ptSLA) { boost::nowide::cerr << "error: cannot export G-code for an FFF configuration" << std::endl; flush_and_exit(1); } else if (opt_key == "export_sla" && printer_technology == ptFFF) { boost::nowide::cerr << "error: cannot export SLA slices for a SLA configuration" << std::endl; flush_and_exit(1); }*/ BOOST_LOG_TRIVIAL(info) << "Need to slice for plate "<= 0) // FIXME: is this sufficient? printf("%3d%s %s\n", s.percent, "% =>", s.text.c_str()); });*/ //BBS: slice every partplate one by one PrintBase *print=NULL; Slic3r::GUI::GCodeResult *gcode_result = NULL; int print_index; for (int index = 0; index < partplate_list.get_plate_count(); index ++) { if ((plate_to_slice != 0) && (plate_to_slice != (index + 1))) { BOOST_LOG_TRIVIAL(info) << "Skip plate " << index+1 << std::endl; continue; } //get the current partplate Slic3r::GUI::PartPlate* part_plate = partplate_list.get_plate(index); part_plate->get_print(&print, &gcode_result, &print_index); /*if (outfile_config.empty()) { outfile = "plate_" + std::to_string(index + 1) + ".gcode"; } else { outfile = "plate_" + std::to_string(index + 1) + "_" + outfile_config + ".gcode"; }*/ //update plate's bounding box to model #if 0 BoundingBoxf3 print_volume = part_plate->get_bounding_box(false); print_volume.max(2) = z; print_volume.min(2) = -1e10; model.update_print_volume_state(print_volume); BOOST_LOG_TRIVIAL(info) << boost::format("print_volume {%1%,%2%,%3%}->{%4%, %5%, %6%}") % print_volume.min(0) % print_volume.min(1) % print_volume.min(2) % print_volume.max(0) % print_volume.max(1) % print_volume.max(2) << std::endl; #else BuildVolume build_volume(part_plate->get_shape(), print_height); model.update_print_volume_state(build_volume); unsigned int count = model.update_print_volume_state(build_volume); if (count == 0) { BOOST_LOG_TRIVIAL(error) << "plate "<< index+1<< ": Nothing to be sliced, Either the print is empty or no object is fully inside the print volume before apply." << std::endl; flush_and_exit(CLI_NO_SUITABLE_OBJECTS); } else { for (ModelObject* model_object : model.objects) for (ModelInstance *i : model_object->instances) if (i->print_volume_state == ModelInstancePVS_Partly_Outside) { BOOST_LOG_TRIVIAL(error) << "plate "<< index+1<< ": Found Object " << model_object->name <<" partly inside, can not be sliced." << std::endl; flush_and_exit(CLI_OBJECTS_PARTLY_INSIDE); } } // BBS: TODO //BOOST_LOG_TRIVIAL(info) << boost::format("print_volume {%1%,%2%,%3%}->{%4%, %5%, %6%}, has %7% printables") % print_volume.min(0) % print_volume.min(1) // % print_volume.min(2) % print_volume.max(0) % print_volume.max(1) % print_volume.max(2) % count << std::endl; #endif //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) (dynamic_cast(print))->auto_assign_extruders(mo); } else { // The default for "filename_format" is good for FDM: "[input_filename_base].gcode" // Replace it with a reasonable SLA default. std::string &format = m_print_config.opt_string("filename_format", true); if (format == static_cast(m_print_config.def()->get("filename_format")->default_value.get())->value) format = "[input_filename_base].SL1"; }*/ print->apply(model, m_print_config); StringObjectException warning; auto err = print->validate(&warning); if (!err.string.empty()) { BOOST_LOG_TRIVIAL(info) << "got error when validate: "<< err.string << std::endl; boost::nowide::cerr << err.string << std::endl; //BBS: continue for other plates //continue; flush_and_exit(CLI_VALIDATE_ERROR); } else if (!warning.string.empty()) BOOST_LOG_TRIVIAL(info) << "got warnings: "<< warning.string << std::endl; if (print->empty()) { BOOST_LOG_TRIVIAL(error) << "plate "<< index+1<< ": Nothing to be sliced, Either the print is empty or no object is fully inside the print volume after apply." << std::endl; flush_and_exit(CLI_NO_SUITABLE_OBJECTS); } else try { std::string outfile_final; BOOST_LOG_TRIVIAL(info) << "start Print::process for partplate "<set_status_callback(cli_status_callback); g_cli_callback_mgr.set_plate_info(index+1, (plate_to_slice== 0)?partplate_list.get_plate_count():1); if (!warning.string.empty()) { PrintBase::SlicingStatus slicing_status{2, warning.string, 0, 0}; cli_status_callback(slicing_status); } } #endif print->process(); if (printer_technology == ptFFF) { // The outfile is processed by a PlaceholderParser. //outfile = part_plate->get_tmp_gcode_path(); if (outfile_dir.empty()) { outfile = part_plate->get_tmp_gcode_path(); } else { outfile = outfile_dir + "/plate_" + std::to_string(index + 1) + ".gcode"; part_plate->set_tmp_gcode_path(outfile); } BOOST_LOG_TRIVIAL(info) << "process finished, will export gcode temporily to " << outfile << std::endl; outfile = (dynamic_cast(print))->export_gcode(outfile, gcode_result, nullptr); //outfile_final = (dynamic_cast(print))->print_statistics().finalize_output_path(outfile); //m_fff_print->export_gcode(m_temp_output_path, m_gcode_result, [this](const ThumbnailsParams& params) { return this->render_thumbnails(params); }); }/* 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) { if (Slic3r::rename_file(outfile, outfile_final)) { boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; flush_and_exit(1); } outfile = outfile_final; }*/ // Run the post-processing scripts if defined. //BBS: TODO, maybe need to open this function later //run_post_process_scripts(outfile, print->full_print_config()); BOOST_LOG_TRIVIAL(info) << "Slicing result exported to " << outfile << std::endl; part_plate->update_slice_result_valid_state(true); #if defined(__linux__) || defined(__LINUX__) if (g_cli_callback_mgr.is_started()) { PrintBase::SlicingStatus slicing_status{100, "Slicing finished"}; cli_status_callback(slicing_status); } #endif } catch (const std::exception &ex) { BOOST_LOG_TRIVIAL(info) << "found slicing or export error for partplate "< > 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_LOG_TRIVIAL(info) << "G-code exported to " << outfile << std::endl; // output some statistics double duration { std::chrono::duration_cast(clock_::now() - t0).count() }; BOOST_LOG_TRIVIAL(info) << 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; flush_and_exit(CLI_UNSUPPORTED_OPERATION); } } if (export_to_3mf) { //BBS: export as bbl 3mf Slic3r::GUI::OpenGLManager opengl_mgr; std::vector thumbnails; std::vector plate_bboxes; PlateDataPtrs plate_data_list; partplate_list.store_to_3mf_structure(plate_data_list); std::vector project_presets; if (!outfile_dir.empty()) { export_3mf_file = outfile_dir + "/"+export_3mf_file; } #if defined(__linux__) || defined(__LINUX__) if (g_cli_callback_mgr.is_started()) { PrintBase::SlicingStatus slicing_status{91, "Generate thumbnails"}; cli_status_callback(slicing_status); } #endif // get type and color for platedata auto* filament_types = dynamic_cast(m_print_config.option("filament_type")); const ConfigOptionStrings* filament_color = dynamic_cast(m_print_config.option("filament_colour")); //auto* filament_id = dynamic_cast(m_print_config.option("filament_ids")); for (int i = 0; i < plate_data_list.size(); i++) { PlateData *plate_data = plate_data_list[i]; for (auto it = plate_data->slice_filaments_info.begin(); it != plate_data->slice_filaments_info.end(); it++) { it->type = filament_types?filament_types->get_at(it->id):"PLA"; it->color = filament_color?filament_color->get_at(it->id):"#FFFFFF"; //it->filament_id = filament_id?filament_id->get_at(it->id):"unknown"; } } std::vector colors; if (filament_color) { colors= filament_color->vserialize(); } else colors.push_back("#FFFFFF"); std::vector> colors_out(colors.size()); unsigned char rgb_color[3] = {}; for (const std::string& color : colors) { Slic3r::GUI::BitmapCache::parse_color(color, rgb_color); size_t color_idx = &color - &colors.front(); colors_out[color_idx] = { float(rgb_color[0]) / 255.f, float(rgb_color[1]) / 255.f, float(rgb_color[2]) / 255.f, 1.f }; } int gl_major, gl_minor, gl_verbos; glfwGetVersion(&gl_major, &gl_minor, &gl_verbos); BOOST_LOG_TRIVIAL(info) << boost::format("opengl version %1%.%2%.%3%")%gl_major %gl_minor %gl_verbos; glfwSetErrorCallback(glfw_callback); int ret = glfwInit(); if (ret == GLFW_FALSE) { int code = glfwGetError(NULL); BOOST_LOG_TRIVIAL(error) << "glfwInit return error, code " <(option))->getInt(); for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++ volume_idx) { const ModelVolume &model_volume = *model_object.volumes[volume_idx]; option = model_volume.config.option("extruder"); if (option) extruder_id = (dynamic_cast(option))->getInt(); //if (!model_volume.is_model_part()) // continue; for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) { const ModelInstance &model_instance = *model_object.instances[instance_idx]; glvolume_collection.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, "volume", true); //glvolume_collection.volumes.back()->geometry_id = key.geometry_id; std::string color = filament_color?filament_color->get_at(extruder_id - 1):"#00FF00"; unsigned char rgb_color[3] = {}; Slic3r::GUI::BitmapCache::parse_color(color, rgb_color); glvolume_collection.volumes.back()->set_render_color( float(rgb_color[0]) / 255.f, float(rgb_color[1]) / 255.f, float(rgb_color[2]) / 255.f, 1.f); std::array new_color; new_color[0] = float(rgb_color[0]) / 255.f; new_color[1] = float(rgb_color[1]) / 255.f; new_color[2] = float(rgb_color[2]) / 255.f; new_color[3] = 1.f; glvolume_collection.volumes.back()->set_color(new_color); } } } ThumbnailsParams thumbnail_params; GLShaderProgram* shader = opengl_mgr.get_shader("gouraud_light"); if (!shader) { BOOST_LOG_TRIVIAL(error) << boost::format("can not get shader for rendering thumbnail"); } else { for (int i = 0; i < partplate_list.get_plate_count(); i++) { Slic3r::GUI::PartPlate *part_plate = partplate_list.get_plate(i); ThumbnailData * thumbnail_data = new ThumbnailData(); unsigned int thumbnail_width = 256, thumbnail_height = 256; const ThumbnailsParams thumbnail_params = {{}, false, true, true, true, i}; switch (Slic3r::GUI::OpenGLManager::get_framebuffers_type()) { case Slic3r::GUI::OpenGLManager::EFramebufferType::Arb: { BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: ARB"); Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer(*thumbnail_data, thumbnail_width, thumbnail_height, thumbnail_params, partplate_list, model.objects, glvolume_collection, colors_out, shader, Slic3r::GUI::Camera::EType::Ortho); break; } case Slic3r::GUI::OpenGLManager::EFramebufferType::Ext: { BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: EXT"); Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer_ext(*thumbnail_data, thumbnail_width, thumbnail_height, thumbnail_params, partplate_list, model.objects, glvolume_collection, colors_out, shader, Slic3r::GUI::Camera::EType::Ortho); break; } default: BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: unknown"); break; } thumbnails.push_back(thumbnail_data); //render calibration thumbnail if (!part_plate->get_slice_result() || !part_plate->is_slice_result_valid()) { BOOST_LOG_TRIVIAL(info) << boost::format("plate %1% doesn't have a valid sliced result, skip it")%(i+1); calibration_thumbnails.push_back(new ThumbnailData()); plate_bboxes.push_back(new PlateBBoxData()); continue; } PrintBase *print_base=NULL; Slic3r::GUI::GCodeResult *gcode_result = NULL; int print_index; part_plate->get_print(&print_base, &gcode_result, &print_index); BuildVolume build_volume(part_plate->get_shape(), print_height); const std::vector& exclude_bounding_box = part_plate->get_exclude_areas(); Print *print = dynamic_cast(print_base); Slic3r::GUI::GCodeViewer gcode_viewer; gcode_viewer.init(ConfigOptionMode::comAdvanced, nullptr); gcode_viewer.load(*gcode_result, *print, build_volume, exclude_bounding_box, false, ConfigOptionMode::comAdvanced, false); std::vector colors; if (filament_color) colors = filament_color->values; gcode_viewer.refresh(*gcode_result, colors); ThumbnailData* calibration_data = new ThumbnailData(); const ThumbnailsParams calibration_params = { {}, false, true, true, true, i }; //BBS fixed size const int cali_thumbnail_width = 2560; const int cali_thumbnail_height = 2560; gcode_viewer.render_calibration_thumbnail(*calibration_data, cali_thumbnail_width, cali_thumbnail_height, calibration_params, partplate_list, opengl_mgr); //generate_calibration_thumbnail(*calibration_data, thumbnail_width, thumbnail_height, calibration_params); //*plate_bboxes[index] = p->generate_first_layer_bbox(); calibration_thumbnails.push_back(calibration_data); PlateBBoxData* plate_bbox = new PlateBBoxData(); std::vector& id_bboxes = plate_bbox->bbox_objs; BoundingBoxf bbox_all; auto seq_print = m_print_config.option>("print_sequence"); if ( seq_print && (seq_print->value == PrintSequence::ByObject)) plate_bbox->is_seq_print = true; auto objects = print->objects(); auto orig = part_plate->get_origin(); Vec2d orig2d = { orig[0], orig[1] }; for (auto obj : objects) { BBoxData data; auto bb_scaled = obj->get_first_layer_bbox(data.area, data.layer_height, data.name); auto bb = unscaled(bb_scaled); bb.min -= orig2d; bb.max -= orig2d; bbox_all.merge(bb); data.area *= (SCALING_FACTOR * SCALING_FACTOR); // unscale area data.id = obj->id().id; data.bbox = { bb.min.x(),bb.min.y(),bb.max.x(),bb.max.y() }; id_bboxes.emplace_back(std::move(data)); } plate_bbox->bbox_all = { bbox_all.min.x(),bbox_all.min.y(),bbox_all.max.x(),bbox_all.max.y() }; plate_bboxes.push_back(plate_bbox); } } } #if defined(__linux__) || defined(__LINUX__) if (g_cli_callback_mgr.is_started()) { PrintBase::SlicingStatus slicing_status{95, "Exporting 3mf"}; cli_status_callback(slicing_status); } #endif BOOST_LOG_TRIVIAL(info) << "will export 3mf to " << export_3mf_file << std::endl; if (! this->export_project(&m_models[0], export_3mf_file, plate_data_list, project_presets, thumbnails, calibration_thumbnails, plate_bboxes, &m_print_config)) { release_PlateData_list(plate_data_list); flush_and_exit(CLI_EXPORT_3MF_ERROR); } release_PlateData_list(plate_data_list); for (unsigned int i = 0; i < thumbnails.size(); i++) delete thumbnails[i]; for (unsigned int i = 0; i < calibration_thumbnails.size(); i++) delete calibration_thumbnails[i]; for (int i = 0; i < plate_bboxes.size(); i++) delete plate_bboxes[i]; } //BBS: release glfw if (export_to_3mf) { glfwTerminate(); } #if defined(__linux__) || defined(__LINUX__) if (g_cli_callback_mgr.is_started()) { PrintBase::SlicingStatus slicing_status{100, "All done, Success"}; cli_status_callback(slicing_status); } g_cli_callback_mgr.stop(); #endif //BBS: flush logs BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", Finished" << std::endl; boost::nowide::cout.flush(); boost::nowide::cerr.flush(); return 0; } bool CLI::setup(int argc, char **argv) { { Slic3r::set_logging_level(1); const char *loglevel = boost::nowide::getenv("BBL_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 BBL_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 BambuStudio process (for example Nahimic, see GH #5573). // We hope that if a DLL is being injected into a BambuStudio 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 BambuStudio process:\n\n"; text += BlacklistedLibraryCheck::get_instance().get_blacklisted_string(); text += L"\n\n" L"BambuStudio 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 BambuStudio.\n" L"For example, ASUS Sonic Studio injects a Nahimic driver, which makes BambuStudio " L"to crash on a secondary monitor"; 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 / "images").string()); set_local_dir((path_resources / "i18n").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); } #if !BBL_RELEASE_TO_PUBLIC { const ConfigOptionInt *opt_loglevel = m_config.opt("debug"); if (opt_loglevel != 0) set_logging_level(opt_loglevel->value); } #endif //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_APP_KEY <<"-"<< SLIC3R_VERSION << ":" << std::endl << "Usage: bambu-studio [ OPTIONS ] [ file.3mf/file.stl ... ]" << std::endl << std::endl << "OPTIONS:" << std::endl; cli_misc_config_def.print_cli_help(boost::nowide::cout, false); cli_transform_config_def.print_cli_help(boost::nowide::cout, false); cli_actions_config_def.print_cli_help(boost::nowide::cout, false); boost::nowide::cout << std::endl << "Print settings priorites:" << std::endl << "\t1) setting values from the command line (highest priority)"<< std::endl << "\t2) setting values loaded with --load_settings and --load_filaments" << std::endl << "\t3) setting values loaded from 3mf(lowest priority)" << 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; //BBS: use bbs 3mf instead of original //case IO::TMF: success = Slic3r::store_bbs_3mf(path.c_str(), &model, nullptr, false); break; default: assert(false); break; } if (success) BOOST_LOG_TRIVIAL(info) << "Model exported to " << path << std::endl; else { boost::nowide::cerr << "Model export to " << path << " failed" << std::endl; return false; } } return true; } //BBS: add export_project function bool CLI::export_project(Model *model, std::string& path, PlateDataPtrs &partplate_data, std::vector& project_presets, std::vector& thumbnails, std::vector& calibration_thumbnails, std::vector& plate_bboxes, const DynamicPrintConfig* config) { //const std::string path = this->output_filepath(*model, IO::TMF); bool success = false; StoreParams store_params; store_params.path = path.c_str(); store_params.model = model; store_params.plate_data_list = partplate_data; store_params.project_presets = project_presets; store_params.config = (DynamicPrintConfig*)config; store_params.thumbnail_data = thumbnails; store_params.calibration_thumbnail_data = calibration_thumbnails; store_params.id_bboxes = plate_bboxes; store_params.strategy = SaveStrategy::Silence|SaveStrategy::WithGcode|SaveStrategy::SplitModel; success = Slic3r::store_bbs_3mf(store_params); if (success) BOOST_LOG_TRIVIAL(info) << "Project exported to " << path << std::endl; else { boost::nowide::cerr << "Project 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_param + ext; } return proposed_path.string(); } //BBS: dump stack debug codes, don't delete currently //#include //#pragma comment(lib, "version.lib") //#pragma comment( lib, "dbghelp.lib" ) /*DWORD main_thread_id; std::string TraceStack() { static const int MAX_STACK_FRAMES = 16; void* pStack[MAX_STACK_FRAMES]; HANDLE process = GetCurrentProcess(); SymInitialize(process, NULL, TRUE); WORD frames = CaptureStackBackTrace(0, MAX_STACK_FRAMES, pStack, NULL); std::ostringstream oss; oss << "stack traceback: frames="<< frames << std::endl; for (WORD i = 0; i < frames; ++i) { DWORD64 address = (DWORD64)(pStack[i]); DWORD64 displacementSym = 0; char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer; pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); pSymbol->MaxNameLen = MAX_SYM_NAME; DWORD displacementLine = 0; IMAGEHLP_LINE64 line; //SymSetOptions(SYMOPT_LOAD_LINES); line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); if (SymFromAddr(process, address, &displacementSym, pSymbol) && SymGetLineFromAddr64(process, address, &displacementLine, &line)) { oss << "\t" << pSymbol->Name << " at " << line.FileName << ":" << line.LineNumber << "(0x" << std::hex << pSymbol->Address << std::dec << ")" << std::endl; } else { oss << "\terror: " << GetLastError() << std::endl; } } return oss.str(); } LONG WINAPI VectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { std::ofstream f; DWORD cur_thread_id = GetCurrentThreadId(); f.open("VectoredExceptionHandler.txt", std::ios::out | std::ios::app); f << "main thread id="< 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(); //BBS: register default exception handler #if 1 SET_DEFULTER_HANDLER(); #else AddVectoredExceptionHandler(1, CBaseException::UnhandledExceptionFilter); #endif // 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 */