BambuStudio/src/BambuStudio.cpp

2628 lines
125 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#ifdef WIN32
// Why?
#define _WIN32_WINNT 0x0502
// The standard Windows includes.
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <wchar.h>
#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 <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <math.h>
#if defined(__linux__) || defined(__LINUX__)
#include <condition_variable>
#include <mutex>
#include <boost/thread.hpp>
//add json logic
#include "nlohmann/json.hpp"
using namespace nlohmann;
#endif
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
#include <boost/nowide/args.hpp>
#include <boost/nowide/cenv.hpp>
#include <boost/nowide/iostream.hpp>
#include <boost/nowide/integration/filesystem.hpp>
#include <boost/dll/runtime_symbol_info.hpp>
#include <boost/log/trivial.hpp>
#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 <wx/stdpaths.h>
#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 <GLFW/glfw3.h>
#ifdef __WXGTK__
#include <X11/Xlib.h>
#endif
#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;*/
std::map<int, std::string> 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_OUT_OF_MEMORY, "Out of memory"},
{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_EXPORT_CACHE_DIRECTORY_CREATE_FAILED, "Objects partly inside"},
{CLI_EXPORT_CACHE_WRITE_FAILED, "export cached slicedata failed"},
{CLI_IMPORT_CACHE_NOT_FOUND, "cached slicedata can not be found"},
{CLI_IMPORT_CACHE_DATA_CAN_NOT_USE, "cached slicedata can not be used"},
{CLI_IMPORT_CACHE_LOAD_FAILED, "load cached slicedata failed"},
{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<std::mutex> lck(m_mutex);
result = m_started;
lck.unlock();
return result;
}
void set_plate_info(int index, int count)
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": index="<<index<< ", count = "<< count;
std::unique_lock<std::mutex> lck(m_mutex);
m_plate_count = count;
m_plate_index = index;
m_progress = 0;
lck.unlock();
return;
}
void notify()
{
if (m_pipe_fd < 0)
return;
json j;
//record the headers
j["plate_index"] = m_plate_index;
j["plate_count"] = m_plate_count;
j["plate_percent"] = m_progress;
j["total_percent"] = m_total_progress;
if (m_warning_step >= 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 "<<ret;
return;
}
void thread_proc()
{
std::unique_lock<std::mutex> 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<std::mutex> 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="<<percent<< ", warning_step=" << warning_step << ", plate_index = "<< m_plate_index<<", plate_count="<< m_plate_count<<", message="<<message;
if (warning_step == -1) {
m_progress = percent;
if ((m_plate_index >= 1)&&(m_plate_index <= m_plate_count)) {
if (m_plate_count <= 1)
m_total_progress = 0.9*m_progress;
else {
m_total_progress = ((float)(m_plate_index - 1)*90)/m_plate_count + ((float)m_progress*0.9)/m_plate_count;
}
}
else
m_total_progress = m_progress;
}
m_message = message;
m_warning_step = warning_step;
m_data_ready = true;
lck.unlock();
m_condition.notify_one();
return;
}
bool start(std::string pipe_name)
{
BOOST_LOG_TRIVIAL(info) << "cli_callback_mgr_t::start enter.";
m_pipe_fd = open(pipe_name.c_str(),O_WRONLY|O_NONBLOCK);
if (m_pipe_fd < 0) {
BOOST_LOG_TRIVIAL(warning) << "could not create pipe for "<<pipe_name;
return false;
}
std::unique_lock<std::mutex> 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<std::mutex> 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<PrinterTechnology> *opt = config.option<ConfigOptionEnum<PrinterTechnology>>("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 "<<ret<<", exit..." << std::endl;\
g_cli_callback_mgr.stop();\
boost::nowide::cout.flush();\
boost::nowide::cerr.flush();\
return(ret);}
#else
#define flush_and_exit(ret) { boost::nowide::cout << __FUNCTION__ << " found error, exit" << std::endl;\
boost::nowide::cout.flush();\
boost::nowide::cerr.flush();\
return(ret);}
#endif
static void glfw_callback(int error_code, const char* description)
{
BOOST_LOG_TRIVIAL(error) << "error_code " <<error_code <<", description: " <<description<< std::endl;
}
int CLI::run(int argc, char **argv)
{
// Mark the main thread for the debugger and for runtime checks.
set_current_thread_name("bambustu_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);
// Also on Linux, we need to tell Xlib that we will be using threads,
// lest we crash when we fire up GStreamer.
XInitThreads();
#endif
// Switch boost::filesystem to utf8.
try {
boost::nowide::nowide_filesystem();
} catch (const std::runtime_error& ex) {
std::string caption = std::string(SLIC3R_APP_FULL_NAME) + " Error";
std::string text = std::string("boost::nowide::nowide_filesystem Failed!\n") + (
SLIC3R_APP_FULL_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 CLI_ENVIRONMENT_ERROR;
}
BOOST_LOG_TRIVIAL(info) << "Current BambuStudio Version "<< SLIC3R_VERSION << std::endl;
/*BOOST_LOG_TRIVIAL(info) << "begin to setup params, argc=" << argc << std::endl;
for (int index=0; index < argc; index++)
BOOST_LOG_TRIVIAL(info) << "index="<< index <<", arg is "<< argv[index] <<std::endl;
int debug_argc = 9;
char *debug_argv[] = {
"E:\work\projects\bambu_release\bamboo_slicer\build_debug\src\Debug\bambu-studio.exe",
"--slice",
"0",
"--load-settings",
"machine.json;process.json",
"--load-filaments",
"filament.json",
"--export-3mf=output.3mf",
"boat.stl"
};
if (! this->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<std::string> &load_configs = m_config.option<ConfigOptionStrings>("load_settings", true)->values;
//BBS: always use ForwardCompatibilitySubstitutionRule::Enable
//const ForwardCompatibilitySubstitutionRule config_substitution_rule = m_config.option<ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>>("config_compatibility", true)->value;
const ForwardCompatibilitySubstitutionRule config_substitution_rule = ForwardCompatibilitySubstitutionRule::Enable;
const std::vector<std::string> &load_filaments = m_config.option<ConfigOptionStrings>("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<std::string> gcode_files;
std::vector<std::string> 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 = "<<params.input_files.size();
}
else {
params.input_files = std::move(m_input_files);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", normal mode, input_files size = "<<params.input_files.size();
}
//BBS: remove GCodeViewer as seperate APP logic
//params.start_as_gcodeviewer = start_as_gcodeviewer;
BOOST_LOG_TRIVIAL(info) << "begin to launch BambuStudio GUI soon";
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
}
BOOST_LOG_TRIVIAL(info) << "start_gui="<< start_gui << std::endl;
//BBS: add plate data related logic
PlateDataPtrs plate_data_src;
int arrange_option;
int plate_to_slice = 0, filament_count = 0;
bool first_file = true, is_bbl_3mf = false, need_arrange = true, has_thumbnails = false, up_config_to_date = false;
Semver file_version;
std::map<size_t, bool> orients_requirement;
std::vector<Preset*> project_presets;
std::string new_printer_name, current_printer_name, new_process_name, current_process_name, current_printer_system_name, current_process_system_name;//, printer_inherits, print_inherits;
std::vector<std::string> upward_compatible_printers, new_print_compatible_printers, current_print_compatible_printers, current_different_settings;
std::vector<std::string> current_filaments_name, current_filaments_system_name, current_inherits_group;
DynamicPrintConfig load_process_config, load_machine_config;
// Read input file(s) if any.
BOOST_LOG_TRIVIAL(info) << "Will start to read model file now, file count :" << m_input_files.size() << "\n";
ConfigOptionInt* slice_option = m_config.option<ConfigOptionInt>("slice");
if (slice_option)
plate_to_slice = slice_option->value;
ConfigOptionBool* uptodate_option = m_config.option<ConfigOptionBool>("uptodate");
if (uptodate_option)
up_config_to_date = uptodate_option->value;
/*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;
}*/
BOOST_LOG_TRIVIAL(info) << "plate_to_slice="<< plate_to_slice << std::endl;
//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_src, &project_presets, &is_bbl_3mf, &file_version, nullptr, nullptr, nullptr, nullptr, nullptr, plate_to_slice);
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_src.size() << "\n";
need_arrange = false;
for (ModelObject* o : model.objects)
{
orients_requirement.insert(std::pair<size_t, bool>(o->id().id, false));
BOOST_LOG_TRIVIAL(info) << "object "<<o->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;
}*/
current_printer_name = config.option<ConfigOptionString>("printer_settings_id")->value;
current_process_name = config.option<ConfigOptionString>("print_settings_id")->value;
current_filaments_name = config.option<ConfigOptionStrings>("filament_settings_id")->values;
ConfigOptionStrings* option_strings = config.option<ConfigOptionStrings>("inherits_group");
if (option_strings) {
current_inherits_group = option_strings->values;
size_t size = current_inherits_group.size();
if (current_inherits_group[size-1].empty())
current_printer_system_name = current_printer_name;
else
current_printer_system_name = current_inherits_group[size-1];
if (current_inherits_group[0].empty())
current_process_system_name = current_process_name;
else
current_process_system_name = current_inherits_group[0];
current_filaments_system_name.resize(size - 2);
for (int index = 1; index < (size - 1); index++) {
if (current_inherits_group[index].empty())
current_filaments_system_name[index-1] = current_filaments_name[index-1];
else
current_filaments_system_name[index-1] = current_inherits_group[index];
}
}
else {
current_printer_system_name = current_printer_name;
current_process_system_name = current_process_name;
current_filaments_system_name = current_filaments_name;
}
filament_count = current_filaments_name.size();
upward_compatible_printers = config.option<ConfigOptionStrings>("upward_compatible_machine", true)->values;
current_print_compatible_printers = config.option<ConfigOptionStrings>("print_compatible_printers", true)->values;
current_different_settings = config.option<ConfigOptionStrings>("different_settings_to_system", true)->values;
}
else
{
need_arrange = true;
for (ModelObject* o : model.objects)
{
orients_requirement.insert(std::pair<size_t, bool>(o->id().id, true));
BOOST_LOG_TRIVIAL(info) << "object "<<o->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 " <<printer_technology<<", from source file "<< file <<std::endl;
flush_and_exit(CLI_INVALID_PRINTER_TECH);
}
if (!config_substitutions.substitutions.empty()) {
BOOST_LOG_TRIVIAL(info) << "Found legacy configuration values, substituted when loading " << file << ":\n";
for (const ConfigSubstitution &subst : config_substitutions.substitutions)
BOOST_LOG_TRIVIAL(info) << "\tkey = \"" << subst.opt_def->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));
}
//}
auto load_system_config_file = [config_substitution_rule](const std::string& file, DynamicPrintConfig& config, std::string& config_type,
std::string& config_name, std::string& filament_id) {
if (! boost::filesystem::exists(file)) {
boost::nowide::cerr << __FUNCTION__<< ": can not find setting file: " << file << std::endl;
return CLI_FILE_NOTFOUND;
}
ConfigSubstitutions config_substitutions;
try {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ":load setting file "<< file << ", with rule "<< config_substitution_rule << std::endl;
std::map<std::string, std::string> key_values;
std::string reason, config_from;
config_substitutions = config.load_from_json(file, config_substitution_rule, key_values, reason);
if (!reason.empty()) {
BOOST_LOG_TRIVIAL(error) <<__FUNCTION__<< ":Can not load config from file "<<file<<"\n";
return CLI_CONFIG_FILE_ERROR;
}
config_name = key_values[BBL_JSON_KEY_NAME];
auto from_iter = key_values.find(BBL_JSON_KEY_FROM);
if (from_iter != key_values.end()) {
config_from = from_iter->second;
}
if (config_from != "system") {
boost::nowide::cerr <<__FUNCTION__ << boost::format(":file %1%'s from %2% unsupported") % file % config_from;
return CLI_CONFIG_FILE_ERROR;
}
auto type_iter = key_values.find(BBL_JSON_KEY_TYPE);
if (type_iter != key_values.end()) {
config_type = type_iter->second;
}
if (config_type == "machine") {
//config.set("printer_settings_id", config_name, true);
//printer_inherits = config.option<ConfigOptionString>("inherits", true)->value;
}
else if (config_type == "process") {
//config.set("print_settings_id", config_name, true);
//print_inherits = config.option<ConfigOptionString>("inherits", true)->value;
}
else if (config_type == "filament") {
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;
}
else {
boost::nowide::cerr <<__FUNCTION__ << boost::format(": unknown config type %1% of file %2% in load-settings") % config_type % file;
return CLI_CONFIG_FILE_ERROR;
}
config.normalize_fdm();
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.erase("inherits");
//config.erase("compatible_printers");
//BOOST_LOG_TRIVIAL(info) << "got printable_area "<< config.option("printable_area")->serialize() << std::endl;
} catch (std::exception &ex) {
boost::nowide::cerr << __FUNCTION__<< ":Loading setting file \"" << file << "\" failed: " << ex.what() << std::endl;
return CLI_CONFIG_FILE_ERROR;
}
return 0;
};
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ":before load settings, file count="<< load_configs.size() << std::endl;
//std::vector<std::string> filament_compatible_printers;
// load config files supplied via --load
for (auto const &file : load_configs) {
DynamicPrintConfig config;
std::string config_type, config_name, filament_id;
int ret = load_system_config_file(file, config, config_type, config_name, filament_id);
if (ret) {
flush_and_exit(ret);
}
if (config_type == "machine") {
if (!new_printer_name.empty()) {
boost::nowide::cerr << "duplicate machine config file: " << file << std::endl;
flush_and_exit(CLI_CONFIG_FILE_ERROR);
}
new_printer_name = config_name;
config.set("printer_settings_id", new_printer_name, true);
//printer_inherits = config.option<ConfigOptionString>("inherits", true)->value;
load_machine_config = std::move(config);
}
else if (config_type == "process") {
if (!new_process_name.empty()) {
boost::nowide::cerr << "duplicate process config file: " << file << std::endl;
flush_and_exit(CLI_CONFIG_FILE_ERROR);
}
new_process_name = config_name;
config.set("print_settings_id", new_process_name, true);
//print_inherits = config.option<ConfigOptionString>("inherits", true)->value;
new_print_compatible_printers = config.option<ConfigOptionStrings>("compatible_printers", true)->values;
load_process_config = std::move(config);
}
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 " <<printer_technology<<", from config "<< file <<std::endl;
flush_and_exit(CLI_INVALID_PRINTER_TECH);
}
}
//load filaments files
int load_filament_count = load_filaments.size();
std::vector<int> load_filaments_index;
std::vector<DynamicPrintConfig> load_filaments_config;
std::vector<std::string> load_filaments_id;
std::vector<std::string> load_filaments_name;
int current_index = 0;
for (int index = 0; index < load_filament_count; index++) {
const std::string& file = load_filaments[index];
current_index++;
if (!file.empty()) {
DynamicPrintConfig config;
std::string config_type, config_name, filament_id;
int ret = load_system_config_file(file, config, config_type, config_name, filament_id);
if (ret) {
flush_and_exit(ret);
}
if (config_type != "filament") {
boost::nowide::cerr <<__FUNCTION__ << boost::format(": unknown config type %1% of file %2% in load-filaments") % config_type % file;
flush_and_exit(CLI_CONFIG_FILE_ERROR);
}
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 " <<printer_technology<<", from filament file "<< file <<std::endl;
flush_and_exit(CLI_INVALID_PRINTER_TECH);
}
load_filaments_id.push_back(filament_id);
load_filaments_name.push_back(config_name);
load_filaments_config.push_back(std::move(config));
load_filaments_index.push_back(current_index);
}
else {
continue;
}
}
if (filament_count == 0)
filament_count = load_filament_count;
//load system config if needed
bool fetch_compatible_values = false, fetch_upward_values = false;
if (is_bbl_3mf && up_config_to_date) {
if (new_printer_name.empty() && !current_printer_system_name.empty()) {
//use the original printer name in 3mf
std::string system_printer_path = resources_dir() + "/profiles/BBL/machine_full/"+current_printer_system_name+".json";
if (! boost::filesystem::exists(system_printer_path)) {
boost::nowide::cerr << __FUNCTION__<< ": can not find system preset file: " << system_printer_path << std::endl;
//use original one
}
else {
DynamicPrintConfig config;
std::string config_type, config_name, filament_id;
int ret = load_system_config_file(system_printer_path, config, config_type, config_name, filament_id);
if (ret)
flush_and_exit(ret);
upward_compatible_printers = config.option<ConfigOptionStrings>("upward_compatible_machine", true)->values;
config.set("printer_settings_id", config_name, true);
load_machine_config = std::move(config);
}
}
else
fetch_upward_values = true;
if (new_process_name.empty() && !current_process_system_name.empty()) {
//use the original printer name in 3mf
std::string system_process_path = resources_dir() + "/profiles/BBL/process_full/"+current_process_system_name+".json";
if (! boost::filesystem::exists(system_process_path)) {
boost::nowide::cerr << __FUNCTION__<< ": can not find system preset file: " << system_process_path << std::endl;
//use original one
}
else {
DynamicPrintConfig config;
std::string config_type, config_name, filament_id;
int ret = load_system_config_file(system_process_path, config, config_type, config_name, filament_id);
if (ret)
flush_and_exit(ret);
current_print_compatible_printers = config.option<ConfigOptionStrings>("compatible_printers", true)->values;
config.set("print_settings_id", config_name, true);
load_process_config = std::move(config);
}
}
else
fetch_compatible_values = true;
if (load_filaments_config.empty() && !current_filaments_system_name.empty()) {
for (int index = 0; index < current_filaments_system_name.size(); index++) {
std::string system_filament_path = resources_dir() + "/profiles/BBL/filament_full/"+current_filaments_system_name[index]+".json";
current_index++;
if (! boost::filesystem::exists(system_filament_path)) {
boost::nowide::cerr << __FUNCTION__<< ": can not find system preset file: " << system_filament_path << std::endl;
continue;
}
DynamicPrintConfig config;
std::string config_type, config_name, filament_id;
int ret = load_system_config_file(system_filament_path, config, config_type, config_name, filament_id);
if (ret)
flush_and_exit(ret);
load_filaments_id.push_back(filament_id);
load_filaments_name.push_back(config_name);
load_filaments_config.push_back(std::move(config));
load_filaments_index.push_back(current_index);
}
}
}
else if (is_bbl_3mf){
fetch_upward_values = true;
fetch_compatible_values = true;
}
//fetch upward_compatible_machine
if (fetch_upward_values) {
if (!current_printer_system_name.empty()) {
//use the original printer name in 3mf
std::string system_printer_path = resources_dir() + "/profiles/BBL/machine_full/"+current_printer_system_name+".json";
if (! boost::filesystem::exists(system_printer_path)) {
boost::nowide::cerr << __FUNCTION__<< ": can not find system preset file: " << system_printer_path << std::endl;
//skip
}
else {
DynamicPrintConfig config;
std::string config_type, config_name, filament_id;
int ret = load_system_config_file(system_printer_path, config, config_type, config_name, filament_id);
if (ret)
flush_and_exit(ret);
upward_compatible_printers = config.option<ConfigOptionStrings>("upward_compatible_machine", true)->values;
}
}
}
//fetch print_compatible_printers
if (fetch_compatible_values) {
if (!current_process_system_name.empty()) {
//use the original printer name in 3mf
std::string system_process_path = resources_dir() + "/profiles/BBL/process_full/"+current_process_system_name+".json";
if (! boost::filesystem::exists(system_process_path)) {
boost::nowide::cerr << __FUNCTION__<< ": can not find system preset file: " << system_process_path << std::endl;
//use original one
}
else {
DynamicPrintConfig config;
std::string config_type, config_name, filament_id;
int ret = load_system_config_file(system_process_path, config, config_type, config_name, filament_id);
if (ret)
flush_and_exit(ret);
current_print_compatible_printers = config.option<ConfigOptionStrings>("compatible_printers", true)->values;
}
}
}
//upwards check
bool process_compatible = true, machine_upwards = false;
if (!new_printer_name.empty()) {
process_compatible = false;
if (!new_process_name.empty()) {
for (int index = 0; index < new_print_compatible_printers.size(); index++) {
if (new_print_compatible_printers[index] == new_printer_name) {
process_compatible = true;
break;
}
}
BOOST_LOG_TRIVIAL(info) << boost::format("new printer %1%, new process %2%, compatible %3%")%new_printer_name %new_process_name %process_compatible;
}
else {
for (int index = 0; index < current_print_compatible_printers.size(); index++) {
if (current_print_compatible_printers[index] == new_printer_name) {
process_compatible = true;
break;
}
}
BOOST_LOG_TRIVIAL(info) << boost::format("new printer %1%, old process %2%, compatible %3%")%new_printer_name %current_process_name %process_compatible;
}
}
else if (!new_process_name.empty()) {
process_compatible = false;
for (int index = 0; index < new_print_compatible_printers.size(); index++) {
if (new_print_compatible_printers[index] == current_printer_name) {
process_compatible = true;
break;
}
}
BOOST_LOG_TRIVIAL(info) << boost::format("old printer %1%, new process %2%, compatible %3%")%current_printer_name %new_process_name %process_compatible;
}
if (!process_compatible && !new_printer_name.empty() && !current_printer_name.empty() && (new_printer_name != current_printer_name)) {
if (upward_compatible_printers.size() > 0) {
for (int index = 0; index < upward_compatible_printers.size(); index++) {
if (upward_compatible_printers[index] == new_printer_name) {
process_compatible = true;
machine_upwards = true;
break;
}
}
if (!process_compatible) {
boost::nowide::cout <<__FUNCTION__ << boost::format(": current 3mf file not support the new printer %1%")%new_printer_name;
flush_and_exit(CLI_3MF_NEW_MACHINE_NOT_SUPPORTED);
}
}
else {
boost::nowide::cout <<__FUNCTION__ << boost::format(": current 3mf file not support upward_compatible_printers, can not change machine preset.");
flush_and_exit(CLI_3MF_NOT_SUPPORT_MACHINE_CHANGE);
}
}
if (!process_compatible) {
boost::nowide::cout <<__FUNCTION__ << boost::format(": process not compatible with printer.");
flush_and_exit(CLI_PROCESS_NOT_COMPATIBLE);
}
//create project embedded preset if needed
Preset *new_preset = NULL;
if (is_bbl_3mf && machine_upwards) {
Preset *current_preset = NULL;
size_t project_presets_count = project_presets.size();
for (int index = 0; index < project_presets_count; index++)
{
if (project_presets[index]->name == current_process_name) {
current_preset = project_presets[index];
break;
}
}
new_preset = new Preset(Preset::TYPE_PRINT, current_process_name);
if (current_preset) {
*new_preset = *current_preset;
std::vector<std::string>& compatible_printers = new_preset->config.option<ConfigOptionStrings>("compatible_printers", true)->values;
bool need_insert = true;
for (int index = 0; index < compatible_printers.size(); index++) {
if (compatible_printers[index] == new_printer_name) {
need_insert = false;
break;
}
}
if (need_insert)
compatible_printers.push_back(new_printer_name);
}
else {
//store a project-embedded preset
const std::vector<std::string>& process_keys = Preset::print_options();
new_preset->config.apply_only(m_print_config, process_keys);
std::vector<std::string>& compatible_printers = new_preset->config.option<ConfigOptionStrings>("compatible_printers", true)->values;
compatible_printers = current_print_compatible_printers;
compatible_printers.push_back(new_printer_name);
if (current_process_system_name != current_process_name) {
std::string& inherits = new_preset->config.option<ConfigOptionString>("inherits", true)->value;
inherits = current_process_system_name;
}
new_preset->is_project_embedded = true;
new_preset->type = Preset::TYPE_PRINT;
//new_preset->name = current_process_name;
}
new_preset->name += "(auto)";
new_preset->config.set("print_settings_id", new_preset->name, true);
m_print_config.set("print_settings_id", new_preset->name, true);
project_presets.push_back(new_preset);
}
//update seperate configs into full config
auto update_full_config = [](DynamicPrintConfig& full_config, const DynamicPrintConfig& config, std::set<std::string>& diff_key_sets) {
for (const t_config_option_key &opt_key : config.keys()) {
if (!diff_key_sets.empty() && (diff_key_sets.find(opt_key) != diff_key_sets.end())) {
//uptodate, diff keys, continue
continue;
}
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 << __FUNCTION__<<": can not found option " <<opt_key<<"from config." <<std::endl;
return CLI_CONFIG_FILE_ERROR;
}
if (opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "model_id" || opt_key == "dev_model_name")
continue;
else {
ConfigOption *dest_opt = full_config.option(opt_key, true);
if (dest_opt == nullptr) {
boost::nowide::cerr << __FUNCTION__<<":can not create option " <<opt_key<<" to full_config "<<std::endl;
return CLI_CONFIG_FILE_ERROR;
}
dest_opt->set(source_opt);
//*dest_opt = *source_opt;
}
}
return 0;
};
std::vector<std::string>& different_settings = m_print_config.option<ConfigOptionStrings>("different_settings_to_system", true)->values;
std::vector<std::string>& inherits_group = m_print_config.option<ConfigOptionStrings>("inherits_group", true)->values;
inherits_group.resize(filament_count + 2, std::string());
different_settings.resize(filament_count + 2, std::string());
//set the machine settings into print config
if (!new_printer_name.empty() || up_config_to_date) {
std::vector<std::string> different_keys;
if (new_printer_name.empty()) {
std::string diff_settings;
if (!different_settings.empty()) {
diff_settings = different_settings[filament_count+1];
Slic3r::unescape_strings_cstyle(diff_settings, different_keys);
}
}
else {
different_settings[filament_count+1] = "";
inherits_group[filament_count+1] = "";
}
std::set<std::string> different_keys_set(different_keys.begin(), different_keys.end());
int ret = update_full_config(m_print_config, load_machine_config, different_keys_set);
if (ret)
flush_and_exit(ret);
}
//set the process settings into print config
std::vector<std::string>& print_compatible_printers = m_print_config.option<ConfigOptionStrings>("print_compatible_printers", true)->values;
if (!new_process_name.empty() || up_config_to_date) {
std::vector<std::string> different_keys;
if (new_process_name.empty()) {
std::string diff_settings;
if (!different_settings.empty()) {
diff_settings = different_settings[0];
Slic3r::unescape_strings_cstyle(diff_settings, different_keys);
int remove_index = -1;
for (int index = 0; index < different_keys.size(); index++) {
if (different_keys[index] == "compatible_printers") {
remove_index = index;
break;
}
}
if (remove_index != -1) {
different_keys[remove_index] = different_keys[different_keys.size() - 1];
different_keys.erase(different_keys.begin() + different_keys.size() - 1);
different_settings[0] = Slic3r::escape_strings_cstyle(different_keys);
}
}
print_compatible_printers = std::move(current_print_compatible_printers);
}
else {
different_settings[0] = "";
inherits_group[0] = "";
print_compatible_printers = std::move(new_print_compatible_printers);
}
std::set<std::string> different_keys_set(different_keys.begin(), different_keys.end());
int ret = update_full_config(m_print_config, load_machine_config, different_keys_set);
if (ret)
flush_and_exit(ret);
}
if (machine_upwards) {
print_compatible_printers.push_back(new_printer_name);
std::string old_setting = different_settings[0];
if (old_setting.empty())
different_settings[0] = "compatible_printers";
else {
std::vector<std::string> different_keys;
Slic3r::unescape_strings_cstyle(old_setting, different_keys);
bool need_insert = true;
for (int index = 0; index < different_keys.size(); index++) {
if (different_keys[index] == "compatible_printers") {
need_insert = false;
break;
}
}
if (need_insert)
different_settings[0] = old_setting + ";compatible_printers";
}
}
//set the filament settings into print config
if ((load_filament_count > 0) || (up_config_to_date))
{
for (int index = 0; index < load_filaments_config.size(); index++) {
DynamicPrintConfig& config = load_filaments_config[index];
int filament_index = load_filaments_index[index];
std::vector<std::string> different_keys;
if (load_filament_count > 0) {
ConfigOptionStrings *opt_filament_settings = static_cast<ConfigOptionStrings *> (m_print_config.option("filament_settings_id", true));
std::string& filament_name = load_filaments_name[index];
ConfigOptionString* filament_name_setting = new ConfigOptionString(filament_name);
if (opt_filament_settings->size() < filament_count)
opt_filament_settings->resize(filament_count, filament_name_setting);
opt_filament_settings->set_at(filament_name_setting, filament_index-1, 0);
config.erase("filament_settings_id");
std::string& filament_id = load_filaments_id[index];
ConfigOptionStrings *opt_filament_ids = static_cast<ConfigOptionStrings *> (m_print_config.option("filament_ids", true));
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, filament_index-1, 0);
different_settings[filament_index] = "";
inherits_group[filament_index] = "";
}
else {
std::string diff_settings;
if (!different_settings.empty()) {
diff_settings = different_settings[filament_index];
Slic3r::unescape_strings_cstyle(diff_settings, different_keys);
}
}
//parse the filament value to index th
//loop through options and apply them
std::set<std::string> different_keys_set(different_keys.begin(), different_keys.end());
for (const t_config_option_key &opt_key : config.keys()) {
if (!different_keys_set.empty() && (different_keys_set.find(opt_key) != different_keys_set.end())) {
//uptodate, diff keys, continue
continue;
}
// 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 " <<opt_key<<"from filament file "<< load_filaments[filament_index -1] <<std::endl;
flush_and_exit(CLI_CONFIG_FILE_ERROR);
}
if (source_opt->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<ConfigOptionStrings*>(opt);
if (opt_vec_dst->size() == 0)
opt_vec_dst->resize(filament_count+2, new ConfigOptionString());
opt_vec_dst->set_at(source_opt, filament_index, 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<ConfigOptionStrings*>(opt);
if (opt_vec_dst->size() == 0)
opt_vec_dst->resize(filament_count, new ConfigOptionString());
opt_vec_dst->set_at(source_opt, filament_index-1, 0);
}
else {
//skip the scalar values
BOOST_LOG_TRIVIAL(warning) << "skip scalar option " <<opt_key<<" from filament file "<< load_filaments[filament_index -1] <<std::endl;
continue;
}
}
else
{
ConfigOption *opt = m_print_config.option(opt_key, true);
if (opt == nullptr) {
// opt_key does not exist in this ConfigBase and it cannot be created, because it is not defined by this->def().
// This is only possible if other is of DynamicConfig type.
boost::nowide::cerr << "can not create option " <<opt_key<<"to config, from filament file "<< load_filaments[filament_index -1] <<std::endl;
flush_and_exit(CLI_CONFIG_FILE_ERROR);
}
ConfigOptionVectorBase* opt_vec_dst = static_cast<ConfigOptionVectorBase*>(opt);
const ConfigOptionVectorBase* opt_vec_src = static_cast<const ConfigOptionVectorBase*>(source_opt);
if (opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "model_id" || opt_key == "dev_model_name" || opt_key == "filament_settings_id")
continue;
else {
opt_vec_dst->set_at(opt_vec_src, filament_index-1, 0);
}
}
}
}
}
//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, "<<orients_requirement.size()<<" objects"<<std::endl;
if (m_models.size() > 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 "<<o->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<ConfigOptionEnum<PrinterTechnology>>("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<ConfigOptionPoints>("printable_area")->values;
Pointfs excluse_areas = m_print_config.opt<ConfigOptionPoints>("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="<<bedfs[2].x() - bedfs[0].x()<<",y="<<bedfs[2].y() - bedfs[0].y()<<",z="<< print_height <<"\n";
}
if (plate_data_src.size() > 0)
{
partplate_list.load_from_3mf_structure(plate_data_src);
}
/*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="<<o->id().id<<std::endl;
//BBS: clear the orient objects lists
orients_requirement[o->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<ConfigOptionPoint>("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<ConfigOptionPoint>("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<ConfigOptionInt>("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<ConfigOptionPoint3>(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<Model> 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<Vec3d, 4> 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<Model> new_models;
for (auto &model : m_models) {
TriangleMesh mesh = model.mesh();
mesh.repair();
std::vector<TriangleMesh> meshes = mesh.cut_by_grid(m_config.option<ConfigOptionPoint>("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";
bool oriented_or_arranged = false;
//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="<<o->id().id<<std::endl;
orientation::orient(o);
oriented_or_arranged = true;
}
else
{
BOOST_LOG_TRIVIAL(info) << "Before process command, no need to orient, object id :" << o->id().id<<std::endl;
}
}
}
//BBS: clear the orient objects lists
orients_requirement.clear();
oriented_or_arranged |= need_arrange;
if (need_arrange)
{
ArrangePolygons selected, unselected, unprintable, locked_aps;
BOOST_LOG_TRIVIAL(info) << "Will arrange now, need_arrange="<<need_arrange<<"\n";
for (Model &model : m_models)
{
//Step-1: prepare arrange polygons
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx)
{
ModelObject* mo = model.objects[oidx];
for (size_t inst_idx = 0; inst_idx < mo->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<double>(ap.translation(X)) % unscale<double>(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, load_slicedata = false, export_slicedata = false, export_slicedata_error = false;
std::string export_3mf_file, load_slice_data_dir, export_slice_data_dir;
std::string outfile_dir = m_config.opt_string("outputdir");
std::vector<ThumbnailData*> 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<ConfigOptionString>("pipe")->value;
g_cli_callback_mgr.start(pipe_name);
#endif
} else if (opt_key == "load_slicedata") {
load_slicedata = true;
load_slice_data_dir = m_config.opt_string(opt_key);
if (export_slicedata) {
BOOST_LOG_TRIVIAL(error) << "should not set load_slicedata and export_slicedata together." << std::endl;
flush_and_exit(CLI_INVALID_PARAMS);
}
} 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 == "uptodate") {
//already processed before
} 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 == "export_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 == "export_slicedata") {
export_slicedata = true;
export_slice_data_dir = m_config.opt_string(opt_key);
if (load_slicedata) {
BOOST_LOG_TRIVIAL(error) << "should not set load_slicedata and export_slicedata together." << std::endl;
flush_and_exit(CLI_INVALID_PARAMS);
}
} else if (opt_key == "slice") {
//BBS: slice 0 means all plates, i means plate i;
plate_to_slice = m_config.option<ConfigOptionInt>("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 "<<plate_to_slice <<", total plate count "<<partplate_list.get_plate_count()<<" partplates!" << std::endl;
// 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;
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());
});*/
//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<PrintBase*>(&fff_print) : static_cast<PrintBase*>(&sla_print);
/*if (! m_config.opt_bool("dont_arrange")) {
if (user_center_specified) {
Vec2d c = m_config.option<ConfigOptionPoint>("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*>(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<const ConfigOptionString*>(m_print_config.def()->get("filename_format")->default_value.get())->value)
format = "[input_filename_base].SL1";
}*/
DynamicPrintConfig new_print_config = m_print_config;
new_print_config.apply(*part_plate->config());
new_print_config.apply(m_extra_config, true);
print->apply(model, new_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 "<<index+1 << std::endl;
#if defined(__linux__) || defined(__LINUX__)
BOOST_LOG_TRIVIAL(info) << "cli callback mgr started: "<<g_cli_callback_mgr.m_started << std::endl;
if (g_cli_callback_mgr.is_started()) {
BOOST_LOG_TRIVIAL(info) << "set print's callback to cli_status_callback.";
print->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
if (load_slicedata) {
std::string plate_dir = load_slice_data_dir+"/"+std::to_string(index+1);
int ret = print->load_cached_data(plate_dir);
if (ret) {
BOOST_LOG_TRIVIAL(warning) << "plate "<< index+1<< ": load Slicing data error, ret=" << ret;
BOOST_LOG_TRIVIAL(warning) << "plate "<< index+1<< ": switch normal slicing";
print->process();
}
else {
BOOST_LOG_TRIVIAL(info) << "plate "<< index+1<< ": load cached data success, go on.";
print->process(true);
BOOST_LOG_TRIVIAL(info) << "plate "<< index+1<< ": finished print::process.";
}
}
else {
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*>(print))->export_gcode(outfile, gcode_result, nullptr);
//outfile_final = (dynamic_cast<Print*>(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
if (export_slicedata) {
BOOST_LOG_TRIVIAL(info) << "plate "<< index+1<< ":will export Slicing data to " << export_slice_data_dir;
std::string plate_dir = export_slice_data_dir+"/"+std::to_string(index+1);
bool with_space = (get_logging_level() >= 4)?true:false;
int ret = print->export_cached_data(plate_dir, with_space);
if (ret) {
BOOST_LOG_TRIVIAL(error) << "plate "<< index+1<< ": export Slicing data error, ret=" << ret;
export_slicedata_error = true;
if (fs::exists(plate_dir))
fs::remove_all(plate_dir);
}
}
} catch (const std::exception &ex) {
BOOST_LOG_TRIVIAL(info) << "found slicing or export error for partplate "<<index+1 << std::endl;
boost::nowide::cerr << ex.what() << std::endl;
//continue;
flush_and_exit(CLI_SLICING_ERROR);
}
}//end for partplate
#if defined(__linux__) || defined(__LINUX__)
if (g_cli_callback_mgr.is_started()) {
int plate_count = (plate_to_slice== 0)?partplate_list.get_plate_count():1;
g_cli_callback_mgr.set_plate_info(plate_count+1, plate_count);
}
#endif
/*
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<double, std::ratio<1> > second_;
std::chrono::time_point<clock_> 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<second_>(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<ThumbnailData *> thumbnails;
std::vector<PlateBBoxData*> plate_bboxes;
PlateDataPtrs plate_data_list;
partplate_list.store_to_3mf_structure(plate_data_list);
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
bool need_regenerate_thumbnail = oriented_or_arranged;
// get type and color for platedata
auto* filament_types = dynamic_cast<const ConfigOptionStrings*>(m_print_config.option("filament_type"));
const ConfigOptionStrings* filament_color = dynamic_cast<const ConfigOptionStrings *>(m_print_config.option("filament_colour"));
//auto* filament_id = dynamic_cast<const ConfigOptionStrings*>(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";
}
if (!plate_data->plate_thumbnail.is_valid()) {
if (!oriented_or_arranged && plate_data_src.size() > i)
plate_data->thumbnail_file = plate_data_src[i]->thumbnail_file;
BOOST_LOG_TRIVIAL(info) << boost::format("thumbnails stage: plate %1%'s thumbnail data is invalid, check the file %2% exist or not")%(i+1) %plate_data->thumbnail_file;
if (plate_data->thumbnail_file.empty() || (!boost::filesystem::exists(plate_data->thumbnail_file))) {
BOOST_LOG_TRIVIAL(info) << boost::format("thumbnails stage: plate %1%'s thumbnail file also not there, need to regenerate")%(i+1);
need_regenerate_thumbnail = true;
}
else {
BOOST_LOG_TRIVIAL(info) << boost::format("thumbnails stage: plate %1%'s thumbnail file exists, no need to regenerate")%(i+1);
}
}
}
if (need_regenerate_thumbnail) {
std::vector<std::string> colors;
if (filament_color) {
colors= filament_color->vserialize();
}
else
colors.push_back("#FFFFFF");
std::vector<std::array<float, 4>> 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 " <<code<< std::endl;
}
else {
BOOST_LOG_TRIVIAL(info) << "glfwInit Success."<< std::endl;
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, gl_major);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, gl_minor);
glfwWindowHint(GLFW_RED_BITS, 8);
glfwWindowHint(GLFW_GREEN_BITS, 8);
glfwWindowHint(GLFW_BLUE_BITS, 8);
glfwWindowHint(GLFW_ALPHA_BITS, 8);
glfwWindowHint(GLFW_VISIBLE, false);
//glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//glfwDisable(GLFW_AUTO_POLL_EVENTS);
#ifdef __WXMAC__
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#else
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);
#endif
#ifdef __linux__
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_OSMESA_CONTEXT_API);
#endif
GLFWwindow* window = glfwCreateWindow(640, 480, "base_window", NULL, NULL);
if (window == NULL)
{
BOOST_LOG_TRIVIAL(error) << "Failed to create GLFW window" << std::endl;
}
else
glfwMakeContextCurrent(window);
}
bool opengl_valid = opengl_mgr.init_gl();
if (!opengl_valid) {
BOOST_LOG_TRIVIAL(error) << "init opengl failed! skip thumbnail generating" << std::endl;
}
else {
BOOST_LOG_TRIVIAL(info) << "glewInit Sucess." << std::endl;
GLVolumeCollection glvolume_collection;
Model &model = m_models[0];
int extruder_id = 1;
for (unsigned int obj_idx = 0; obj_idx < (unsigned int)model.objects.size(); ++ obj_idx) {
const ModelObject &model_object = *model.objects[obj_idx];
const ConfigOption* option = model_object.config.option("extruder");
if (option)
extruder_id = (dynamic_cast<const ConfigOptionInt *>(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<const ConfigOptionInt *>(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<float, 4> 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);
PlateData *plate_data = plate_data_list[i];
if (plate_data->plate_thumbnail.is_valid()) {
thumbnails.push_back(&plate_data->plate_thumbnail);
BOOST_LOG_TRIVIAL(info) << boost::format("plate %1% has a valid thumbnail, width %2%, height %3% directly using it")%(i+1) %plate_data->plate_thumbnail.width %plate_data->plate_thumbnail.height;
continue;
}
BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%'s thumbnail, need to regenerate")%(i+1);
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);
BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%'s thumbnail,finished rendering")%(i+1);
}
}
}
//BBS: release glfw
glfwTerminate();
}
else {
for (int i = 0; i < partplate_list.get_plate_count(); i++) {
PlateData *plate_data = plate_data_list[i];
if (plate_data->plate_thumbnail.is_valid()) {
thumbnails.push_back(&plate_data->plate_thumbnail);
BOOST_LOG_TRIVIAL(info) << boost::format("plate %1% has a valid thumbnail data, width %2%, height %3% directly using it")%(i+1) %plate_data->plate_thumbnail.width %plate_data->plate_thumbnail.height;
}
}
}
//generate first layer bboxes
for (int i = 0; i < partplate_list.get_plate_count(); i++) {
Slic3r::GUI::PartPlate *part_plate = partplate_list.get_plate(i);
//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);
Print *print = dynamic_cast<Print *>(print_base);
//don't render calibration picture
/*BuildVolume build_volume(part_plate->get_shape(), print_height);
const std::vector<BoundingBoxf3>& exclude_bounding_box = part_plate->get_exclude_areas();
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<std::string> 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<BBoxData>& id_bboxes = plate_bbox->bbox_objs;
BoundingBoxf bbox_all;
auto seq_print = m_print_config.option<ConfigOptionEnum<PrintSequence>>("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));
}
// add wipe tower bounding box
if (print->has_wipe_tower()) {
BBoxData data;
auto wt_corners = print->first_layer_wipe_tower_corners();
// when loading gcode.3mf, wipe tower info may not be correct
if (!wt_corners.empty()) {
BoundingBox bb_scaled = {wt_corners[0], wt_corners[2]};
auto bb = unscaled(bb_scaled);
bb.min -= orig2d;
bb.max -= orig2d;
bbox_all.merge(bb);
data.name = "wipe_tower";
data.id = partplate_list.get_curr_plate()->get_index() + 1000;
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];
}
if (plate_data_src.size() > 0)
{
release_PlateData_list(plate_data_src);
}
#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<ConfigOptionInt>("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<Preset*>& project_presets, std::vector<ThumbnailData*>& thumbnails, std::vector<ThumbnailData*>& calibration_thumbnails, std::vector<PlateBBoxData*>& 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 <dbghelp.h>
//#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="<<main_thread_id<<", current thread_id="<< cur_thread_id << std::endl;
f << std::hex << pExceptionInfo->ExceptionRecord->ExceptionCode << std::endl;
f << TraceStack();
f.flush();
f.close();
return EXCEPTION_CONTINUE_SEARCH;
}*/
#if defined(_MSC_VER) || defined(__MINGW32__)
extern "C" {
__declspec(dllexport) int __stdcall bambustu_main(int argc, wchar_t **argv)
{
// Convert wchar_t arguments to UTF8.
std::vector<std::string> argv_narrow;
std::vector<char*> 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 BBL_RELEASE_TO_PUBLIC
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 */