2078 lines
96 KiB
C++
2078 lines
96 KiB
C++
#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 SLIC3R_GUI
|
|
#include "slic3r/GUI/GUI_Init.hpp"
|
|
#endif /* SLIC3R_GUI */
|
|
|
|
using namespace Slic3r;
|
|
|
|
/*typedef struct _error_message{
|
|
int code;
|
|
std::string message;
|
|
}error_message;*/
|
|
|
|
#define CLI_SUCCESS 0
|
|
#define CLI_ENVIRONMENT_ERROR -1
|
|
#define CLI_INVALID_PARAMS -2
|
|
#define CLI_FILE_NOTFOUND -3
|
|
#define CLI_FILELIST_INVALID_ORDER -4
|
|
#define CLI_CONFIG_FILE_ERROR -5
|
|
#define CLI_DATA_FILE_ERROR -6
|
|
#define CLI_INVALID_PRINTER_TECH -7
|
|
#define CLI_UNSUPPORTED_OPERATION -8
|
|
|
|
#define CLI_COPY_OBJECTS_ERROR -9
|
|
#define CLI_SCALE_TO_FIT_ERROR -10
|
|
#define CLI_EXPORT_STL_ERROR -11
|
|
#define CLI_EXPORT_OBJ_ERROR -12
|
|
#define CLI_EXPORT_3MF_ERROR -13
|
|
|
|
#define CLI_NO_SUITABLE_OBJECTS -50
|
|
#define CLI_VALIDATE_ERROR -51
|
|
#define CLI_OBJECTS_PARTLY_INSIDE -52
|
|
|
|
#define CLI_SLICING_ERROR -100
|
|
|
|
|
|
|
|
std::map<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_NO_SUITABLE_OBJECTS, "Found no objects in print volume to slice"},
|
|
{CLI_VALIDATE_ERROR, "Validate print error"},
|
|
{CLI_OBJECTS_PARTLY_INSIDE, "Objects partly inside"},
|
|
{CLI_SLICING_ERROR, "Slice error"}
|
|
};
|
|
|
|
#if defined(__linux__) || defined(__LINUX__)
|
|
#define PIPE_BUFFER_SIZE 128
|
|
|
|
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;
|
|
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)
|
|
{
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": percent="<<percent<< ", plate_index = "<< m_plate_index<<", plate_count="<< m_plate_count<<", message="<<message;
|
|
std::unique_lock<std::mutex> lck(m_mutex);
|
|
if (!m_started) {
|
|
lck.unlock();
|
|
return;
|
|
}
|
|
|
|
if (m_progress >= percent) {
|
|
//already update before
|
|
lck.unlock();
|
|
return;
|
|
}
|
|
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);
|
|
#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",
|
|
"--export-3mf=output.3mf",
|
|
"--curr-bed-type",
|
|
"Engineering Plate",
|
|
"--load-filaments",
|
|
"GFSU00.json",
|
|
"fujian.3mf"
|
|
};
|
|
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) << "before load settings, file count="<< load_configs.size() << std::endl;
|
|
// load config files supplied via --load
|
|
for (auto const &file : load_configs) {
|
|
if (! boost::filesystem::exists(file)) {
|
|
boost::nowide::cerr << "can not find setting file: " << file << std::endl;
|
|
flush_and_exit(CLI_FILE_NOTFOUND);
|
|
}
|
|
DynamicPrintConfig config;
|
|
ConfigSubstitutions config_substitutions;
|
|
try {
|
|
BOOST_LOG_TRIVIAL(info) << "load setting file "<< file << ", with rule "<< config_substitution_rule << std::endl;
|
|
std::map<std::string, std::string> key_values;
|
|
std::string reason;
|
|
|
|
config_substitutions = config.load_from_json(file, config_substitution_rule, key_values, reason);
|
|
if (!reason.empty()) {
|
|
BOOST_LOG_TRIVIAL(error) << "Can not load config from file "<<file<<"\n";
|
|
flush_and_exit(CLI_CONFIG_FILE_ERROR);
|
|
}
|
|
//BOOST_LOG_TRIVIAL(info) << "got printable_area "<< config.option("printable_area")->serialize() << std::endl;
|
|
} catch (std::exception &ex) {
|
|
boost::nowide::cerr << "Loading setting file \"" << file << "\" failed: " << ex.what() << std::endl;
|
|
flush_and_exit(CLI_CONFIG_FILE_ERROR);
|
|
}
|
|
if (! config_substitutions.empty()) {
|
|
BOOST_LOG_TRIVIAL(info) << "Found legacy configuration values, substituted when loading " << file << ":\n";
|
|
for (const ConfigSubstitution &subst : config_substitutions)
|
|
BOOST_LOG_TRIVIAL(info) << "\tkey = \"" << subst.opt_def->opt_key << "\"\t old_value = \"" << subst.old_value << "\tnew_value = \"" << subst.new_value->serialize() << "\"\n";
|
|
}
|
|
else {
|
|
BOOST_LOG_TRIVIAL(info) << "no substitutions performed from file " << file << "\n";
|
|
}
|
|
config.normalize_fdm();
|
|
PrinterTechnology other_printer_technology = get_printer_technology(config);
|
|
if (printer_technology == ptUnknown) {
|
|
printer_technology = other_printer_technology;
|
|
}
|
|
|
|
if ((printer_technology != other_printer_technology)&&(other_printer_technology != ptUnknown)){
|
|
boost::nowide::cerr << "invalid printer_technology " <<printer_technology<<", from config "<< file <<std::endl;
|
|
flush_and_exit(CLI_INVALID_PRINTER_TECH);
|
|
}
|
|
m_print_config.apply(config);
|
|
}
|
|
|
|
//load filaments files
|
|
int filament_count = load_filaments.size();
|
|
for (int index = 0; index < filament_count; index++) {
|
|
const std::string& file = load_filaments[index];
|
|
if (! boost::filesystem::exists(file)) {
|
|
boost::nowide::cerr << "can not find filament file: " << file << std::endl;
|
|
flush_and_exit(CLI_FILE_NOTFOUND);
|
|
}
|
|
DynamicPrintConfig config;
|
|
ConfigSubstitutions config_substitutions;
|
|
std::map<std::string, std::string> key_values;
|
|
try {
|
|
BOOST_LOG_TRIVIAL(info) << "load filament file "<< file << ", with rule "<< config_substitution_rule << std::endl;
|
|
std::string reason;
|
|
config_substitutions = config.load_from_json(file, config_substitution_rule, key_values, reason);
|
|
if (!reason.empty()) {
|
|
BOOST_LOG_TRIVIAL(error) << "Can not load filament config from file "<<file<<"\n";
|
|
flush_and_exit(CLI_CONFIG_FILE_ERROR);
|
|
}
|
|
//BOOST_LOG_TRIVIAL(info) << "got printable_area "<< config.option("printable_area")->serialize() << std::endl;
|
|
} catch (std::exception &ex) {
|
|
boost::nowide::cerr << "Loading filament file \"" << file << "\" failed: " << ex.what() << std::endl;
|
|
flush_and_exit(CLI_CONFIG_FILE_ERROR);
|
|
}
|
|
if (! config_substitutions.empty()) {
|
|
BOOST_LOG_TRIVIAL(info) << "Found legacy configuration values, substituted when loading " << file << ":\n";
|
|
for (const ConfigSubstitution &subst : config_substitutions)
|
|
BOOST_LOG_TRIVIAL(info) << "\tkey = \"" << subst.opt_def->opt_key << "\"\t old_value = \"" << subst.old_value << "\tnew_value = \"" << subst.new_value->serialize() << "\"\n";
|
|
}
|
|
else {
|
|
BOOST_LOG_TRIVIAL(info) << "no substitutions performed from file " << file << "\n";
|
|
}
|
|
config.normalize_fdm();
|
|
PrinterTechnology other_printer_technology = get_printer_technology(config);
|
|
if (printer_technology == ptUnknown) {
|
|
printer_technology = other_printer_technology;
|
|
}
|
|
|
|
if ((printer_technology != other_printer_technology) && (other_printer_technology != ptUnknown)) {
|
|
boost::nowide::cerr << "invalid printer_technology " <<printer_technology<<", from filament file "<< file <<std::endl;
|
|
flush_and_exit(CLI_INVALID_PRINTER_TECH);
|
|
}
|
|
ConfigOptionStrings *opt_filament_settings = static_cast<ConfigOptionStrings *> (m_print_config.option("filament_settings_id", true));
|
|
ConfigOptionStrings *opt_filament_settings_src = static_cast<ConfigOptionStrings *>(config.option("filament_settings_id", false));
|
|
if (opt_filament_settings_src)
|
|
opt_filament_settings->set_at(opt_filament_settings_src, index, 0);
|
|
else {
|
|
std::string name = file;
|
|
name.erase(name.size() - 5);
|
|
ConfigOptionString option(name);
|
|
opt_filament_settings->set_at(&option, index, 0);
|
|
}
|
|
std::string filament_id;
|
|
ConfigOptionStrings *opt_filament_ids = static_cast<ConfigOptionStrings *> (m_print_config.option("filament_ids", true));
|
|
auto filament_id_iter = key_values.find(BBL_JSON_KEY_FILAMENT_ID);
|
|
if (filament_id_iter != key_values.end())
|
|
filament_id = filament_id_iter->second;
|
|
ConfigOptionString* filament_id_setting = new ConfigOptionString(filament_id);
|
|
if (opt_filament_ids->size() < filament_count)
|
|
opt_filament_ids->resize(filament_count, filament_id_setting);
|
|
opt_filament_ids->set_at(filament_id_setting, index, 0);
|
|
//parse the filament value to index th
|
|
//loop through options and apply them
|
|
for (const t_config_option_key &opt_key : config.keys()) {
|
|
// Create a new option with default value for the key.
|
|
// If the key is not in the parameter definition, or this ConfigBase is a static type and it does not support the parameter,
|
|
// an exception is thrown if not ignore_nonexistent.
|
|
|
|
const ConfigOption *source_opt = config.option(opt_key);
|
|
if (source_opt == nullptr) {
|
|
// The key was not found in the source config, therefore it will not be initialized!
|
|
boost::nowide::cerr << "can not found option " <<opt_key<<"from filament file "<< file <<std::endl;
|
|
flush_and_exit(CLI_CONFIG_FILE_ERROR);
|
|
}
|
|
if (opt_key == "compatible_prints" || opt_key == "compatible_printers")
|
|
continue;
|
|
else 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(1, new ConfigOptionString());
|
|
opt_vec_dst->set_at(source_opt, index+1, 0);
|
|
}
|
|
else if (opt_key == "compatible_prints_condition") {
|
|
ConfigOption *opt = m_print_config.option("compatible_process_expression_group", true);
|
|
ConfigOptionStrings* opt_vec_dst = static_cast<ConfigOptionStrings*>(opt);
|
|
if (opt_vec_dst->size() == 0)
|
|
opt_vec_dst->resize(1, new ConfigOptionString());
|
|
opt_vec_dst->set_at(source_opt, index, 0);
|
|
}
|
|
else {
|
|
//skip the scalar values
|
|
BOOST_LOG_TRIVIAL(info) << "skip scalar option " <<opt_key<<" from filament file "<< file <<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 "<< file <<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 == "bed_temperature" || opt_key == "bed_temperature_initial_layer") {
|
|
const ConfigOptionInts* bed_temp_opt = dynamic_cast<const ConfigOptionInts*>(opt_vec_src);
|
|
for (size_t type = 0; type < (size_t)BedType::btCount; type++) {
|
|
if (type < bed_temp_opt->size())
|
|
opt_vec_dst->set_at(bed_temp_opt, index * BedType::btCount + type, type);
|
|
else
|
|
// BBS FIXME: set bed temperature to 0 for new bed types
|
|
opt_vec_dst->set_at(new ConfigOptionInt(0), index * BedType::btCount + type, type);
|
|
}
|
|
}
|
|
else if (opt_key == "compatible_prints" || opt_key == "compatible_printers")
|
|
continue;
|
|
else {
|
|
opt_vec_dst->set_at(opt_vec_src, index, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// are we starting as gcodeviewer ?
|
|
/*for (auto it = m_actions.begin(); it != m_actions.end(); ++it) {
|
|
if (*it == "gcodeviewer") {
|
|
start_gui = true;
|
|
start_as_gcodeviewer = true;
|
|
m_actions.erase(it);
|
|
break;
|
|
}
|
|
}*/
|
|
|
|
BOOST_LOG_TRIVIAL(info) << "start_gui="<< start_gui << std::endl;
|
|
|
|
//BBS: add plate data related logic
|
|
PlateDataPtrs plate_data;
|
|
int arrange_option;
|
|
bool first_file = true, is_bbl_3mf = false, need_arrange = true;
|
|
Semver file_version;
|
|
std::map<size_t, bool> orients_requirement;
|
|
std::vector<Preset*> project_presets;
|
|
|
|
// Read input file(s) if any.
|
|
BOOST_LOG_TRIVIAL(info) << "Will start to read model file now, file count :" << m_input_files.size() << "\n";
|
|
/*for (const std::string& file : m_input_files)
|
|
if (is_gcode_file(file) && boost::filesystem::exists(file)) {
|
|
start_as_gcodeviewer = true;
|
|
BOOST_LOG_TRIVIAL(info) << "found a gcode file:" << file << ", will start as gcode viewer\n";
|
|
break;
|
|
}*/
|
|
//if (!start_as_gcodeviewer) {
|
|
for (const std::string& file : m_input_files) {
|
|
if (!boost::filesystem::exists(file)) {
|
|
boost::nowide::cerr << "No such file: " << file << std::endl;
|
|
flush_and_exit(CLI_FILE_NOTFOUND);
|
|
}
|
|
Model model;
|
|
//BBS: add plate related logic
|
|
//bool load_aux = false;
|
|
BOOST_LOG_TRIVIAL(info) << "read model file:" << file << "\n";
|
|
try {
|
|
// When loading an AMF or 3MF, config is imported as well, including the printer technology.
|
|
DynamicPrintConfig config;
|
|
ConfigSubstitutionContext config_substitutions(config_substitution_rule);
|
|
|
|
//FIXME should we check the version here? // | LoadStrategy::CheckVersion ?
|
|
is_bbl_3mf = false;
|
|
LoadStrategy strategy;
|
|
if (boost::algorithm::iends_with(file, ".3mf") && first_file) {
|
|
strategy = LoadStrategy::LoadModel | LoadStrategy::LoadConfig|LoadStrategy::AddDefaultInstances | LoadStrategy::LoadAuxiliary;
|
|
//load_aux = true;
|
|
}
|
|
else
|
|
strategy = LoadStrategy::LoadModel | LoadStrategy::AddDefaultInstances;
|
|
// BBS: adjust whebackup
|
|
//LoadStrategy strategy = LoadStrategy::LoadModel | LoadStrategy::LoadConfig|LoadStrategy::AddDefaultInstances;
|
|
//if (load_aux) strategy = strategy | LoadStrategy::LoadAuxiliary;
|
|
model = Model::read_from_file(file, &config, &config_substitutions, strategy, &plate_data, &project_presets, &is_bbl_3mf, &file_version);
|
|
if (is_bbl_3mf)
|
|
{
|
|
if (!first_file)
|
|
{
|
|
BOOST_LOG_TRIVIAL(info) << "The BBL 3mf file should be placed at the first position, filename=" << file << "\n";
|
|
flush_and_exit(CLI_FILELIST_INVALID_ORDER);
|
|
}
|
|
BOOST_LOG_TRIVIAL(info) << "the first file is a 3mf, got plate count:" << plate_data.size() << "\n";
|
|
need_arrange = false;
|
|
for (ModelObject* o : model.objects)
|
|
{
|
|
orients_requirement.insert(std::pair<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;
|
|
}*/
|
|
}
|
|
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));
|
|
}
|
|
//}
|
|
|
|
//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.size() > 0)
|
|
{
|
|
partplate_list.load_from_3mf_structure(plate_data);
|
|
release_PlateData_list(plate_data);
|
|
}
|
|
/*for (ModelObject *model_object : m_models[0].objects)
|
|
for (ModelInstance *model_instance : model_object->instances)
|
|
{
|
|
const Vec3d &instance_offset = model_instance->get_offset();
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("instance %1% transform {%2%,%3%,%4%} at %5%:%6%")% model_object->name % instance_offset.x() % instance_offset.y() %instance_offset.z() % __FUNCTION__ % __LINE__<< std::endl;
|
|
}*/
|
|
|
|
// Loop through transform options.
|
|
bool user_center_specified = false;
|
|
Points beds = get_bed_shape(m_print_config);
|
|
ArrangeParams arrange_cfg;
|
|
arrange_cfg.min_obj_distance = scaled(min_object_distance(m_print_config));
|
|
|
|
BOOST_LOG_TRIVIAL(info) << "will start transforms, commands count " << m_transforms.size() << "\n";
|
|
for (auto const &opt_key : m_transforms) {
|
|
BOOST_LOG_TRIVIAL(info) << "process transform " << opt_key << "\n";
|
|
if (opt_key == "merge") {
|
|
//BBS: always merge, do nothing here
|
|
/*Model m;
|
|
for (auto& model : m_models)
|
|
for (ModelObject* o : model.objects)
|
|
m.add_object(*o);
|
|
// Rearrange instances unless --dont-arrange is supplied
|
|
if (!m_config.opt_bool("dont_arrange")) {
|
|
m.add_default_instances();
|
|
if (this->has_print_action())
|
|
arrange_objects(m, bed, arrange_cfg);
|
|
else
|
|
arrange_objects(m, InfiniteBed{}, arrange_cfg);
|
|
}
|
|
m_models.clear();
|
|
m_models.emplace_back(std::move(m));*/
|
|
}
|
|
else if (opt_key == "convert_unit") {
|
|
for (auto& model : m_models) {
|
|
if (model.looks_like_saved_in_meters()) {
|
|
BOOST_LOG_TRIVIAL(info) << "convert from meter to millimeter\n";
|
|
model.convert_from_meters(true);
|
|
}
|
|
else if (model.looks_like_imperial_units()) {
|
|
BOOST_LOG_TRIVIAL(info) << "convert from inch to millimeter\n";
|
|
model.convert_from_imperial_units(true);
|
|
}
|
|
}
|
|
}
|
|
else if (opt_key == "orient") {
|
|
for (auto& model : m_models)
|
|
for (ModelObject* o : model.objects)
|
|
{
|
|
// coconut: always orient instance instead of object
|
|
for (ModelInstance* mi : o->instances)
|
|
{
|
|
orientation::orient(mi);
|
|
}
|
|
BOOST_LOG_TRIVIAL(info) << "orient object, name=" << o->name <<",id="<<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";
|
|
//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);
|
|
}
|
|
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();
|
|
|
|
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;
|
|
int plate_to_slice = 0;
|
|
std::string export_3mf_file;
|
|
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 == "export_settings") {
|
|
//FIXME check for mixing the FFF / SLA parameters.
|
|
// or better save fff_print_config vs. sla_print_config
|
|
//m_print_config.save(m_config.opt_string("save"));
|
|
m_print_config.save_to_json(m_config.opt_string(opt_key), std::string("project_settings"), std::string("project"), std::string(SLIC3R_VERSION));
|
|
} else if (opt_key == "info") {
|
|
// --info works on unrepaired model
|
|
for (Model &model : m_models) {
|
|
model.add_default_instances();
|
|
model.print_info();
|
|
}
|
|
} else if (opt_key == "export_stl") {
|
|
for (auto &model : m_models)
|
|
model.add_default_instances();
|
|
if (! this->export_models(IO::STL))
|
|
flush_and_exit(CLI_EXPORT_STL_ERROR);
|
|
} else if (opt_key == "expor1t_obj") {
|
|
for (auto &model : m_models)
|
|
model.add_default_instances();
|
|
if (! this->export_models(IO::OBJ))
|
|
flush_and_exit(CLI_EXPORT_OBJ_ERROR);
|
|
}/* else if (opt_key == "export_amf") {
|
|
if (! this->export_models(IO::AMF))
|
|
return 1;
|
|
} */else if (opt_key == "export_3mf") {
|
|
export_to_3mf = true;
|
|
export_3mf_file = m_config.opt_string(opt_key);
|
|
//} else if (opt_key == "export_gcode" || opt_key == "export_sla" || opt_key == "slice") {
|
|
} else if (opt_key == "slice") {
|
|
//BBS: slice 0 means all plates, i means plate i;
|
|
plate_to_slice = m_config.option<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";
|
|
}*/
|
|
print->apply(model, m_print_config);
|
|
StringObjectException warning;
|
|
auto err = print->validate(&warning);
|
|
if (!err.string.empty()) {
|
|
BOOST_LOG_TRIVIAL(info) << "got error when validate: "<< err.string << std::endl;
|
|
boost::nowide::cerr << err.string << std::endl;
|
|
//BBS: continue for other plates
|
|
//continue;
|
|
flush_and_exit(CLI_VALIDATE_ERROR);
|
|
}
|
|
else if (!warning.string.empty())
|
|
BOOST_LOG_TRIVIAL(info) << "got warnings: "<< warning.string << std::endl;
|
|
|
|
if (print->empty()) {
|
|
BOOST_LOG_TRIVIAL(error) << "plate "<< index+1<< ": Nothing to be sliced, Either the print is empty or no object is fully inside the print volume after apply." << std::endl;
|
|
flush_and_exit(CLI_NO_SUITABLE_OBJECTS);
|
|
}
|
|
else
|
|
try {
|
|
std::string outfile_final;
|
|
BOOST_LOG_TRIVIAL(info) << "start Print::process for partplate "<<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);
|
|
}
|
|
#endif
|
|
print->process();
|
|
if (printer_technology == ptFFF) {
|
|
// The outfile is processed by a PlaceholderParser.
|
|
//outfile = part_plate->get_tmp_gcode_path();
|
|
if (outfile_dir.empty()) {
|
|
outfile = part_plate->get_tmp_gcode_path();
|
|
}
|
|
else {
|
|
outfile = outfile_dir + "/plate_" + std::to_string(index + 1) + ".gcode";
|
|
part_plate->set_tmp_gcode_path(outfile);
|
|
}
|
|
BOOST_LOG_TRIVIAL(info) << "process finished, will export gcode temporily to " << outfile << std::endl;
|
|
outfile = (dynamic_cast<Print*>(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
|
|
} 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);
|
|
std::vector<Preset*> project_presets;
|
|
if (!outfile_dir.empty()) {
|
|
export_3mf_file = outfile_dir + "/"+export_3mf_file;
|
|
}
|
|
|
|
#if defined(__linux__) || defined(__LINUX__)
|
|
if (g_cli_callback_mgr.is_started()) {
|
|
PrintBase::SlicingStatus slicing_status{91, "Generate thumbnails"};
|
|
cli_status_callback(slicing_status);
|
|
}
|
|
#endif
|
|
|
|
// get type and color for platedata
|
|
auto* filament_types = dynamic_cast<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";
|
|
}
|
|
}
|
|
|
|
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);
|
|
ThumbnailData * thumbnail_data = new ThumbnailData();
|
|
unsigned int thumbnail_width = 256, thumbnail_height = 256;
|
|
const ThumbnailsParams thumbnail_params = {{}, false, true, true, true, i};
|
|
|
|
switch (Slic3r::GUI::OpenGLManager::get_framebuffers_type())
|
|
{
|
|
case Slic3r::GUI::OpenGLManager::EFramebufferType::Arb:
|
|
{
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: ARB");
|
|
Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer(*thumbnail_data,
|
|
thumbnail_width, thumbnail_height, thumbnail_params,
|
|
partplate_list, model.objects, glvolume_collection, colors_out, shader, Slic3r::GUI::Camera::EType::Ortho);
|
|
break;
|
|
}
|
|
case Slic3r::GUI::OpenGLManager::EFramebufferType::Ext:
|
|
{
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: EXT");
|
|
Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer_ext(*thumbnail_data,
|
|
thumbnail_width, thumbnail_height, thumbnail_params,
|
|
partplate_list, model.objects, glvolume_collection, colors_out, shader, Slic3r::GUI::Camera::EType::Ortho);
|
|
break;
|
|
}
|
|
default:
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: unknown");
|
|
break;
|
|
}
|
|
thumbnails.push_back(thumbnail_data);
|
|
|
|
//render calibration thumbnail
|
|
if (!part_plate->get_slice_result() || !part_plate->is_slice_result_valid()) {
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("plate %1% doesn't have a valid sliced result, skip it")%(i+1);
|
|
calibration_thumbnails.push_back(new ThumbnailData());
|
|
plate_bboxes.push_back(new PlateBBoxData());
|
|
continue;
|
|
}
|
|
PrintBase *print_base=NULL;
|
|
Slic3r::GUI::GCodeResult *gcode_result = NULL;
|
|
int print_index;
|
|
part_plate->get_print(&print_base, &gcode_result, &print_index);
|
|
|
|
BuildVolume build_volume(part_plate->get_shape(), print_height);
|
|
const std::vector<BoundingBoxf3>& exclude_bounding_box = part_plate->get_exclude_areas();
|
|
Print *print = dynamic_cast<Print *>(print_base);
|
|
Slic3r::GUI::GCodeViewer gcode_viewer;
|
|
gcode_viewer.init(ConfigOptionMode::comAdvanced, nullptr);
|
|
gcode_viewer.load(*gcode_result, *print, build_volume, exclude_bounding_box, false, ConfigOptionMode::comAdvanced, false);
|
|
|
|
std::vector<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));
|
|
}
|
|
plate_bbox->bbox_all = { bbox_all.min.x(),bbox_all.min.y(),bbox_all.max.x(),bbox_all.max.y() };
|
|
plate_bboxes.push_back(plate_bbox);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(__linux__) || defined(__LINUX__)
|
|
if (g_cli_callback_mgr.is_started()) {
|
|
PrintBase::SlicingStatus slicing_status{95, "Exporting 3mf"};
|
|
cli_status_callback(slicing_status);
|
|
}
|
|
#endif
|
|
|
|
BOOST_LOG_TRIVIAL(info) << "will export 3mf to " << export_3mf_file << std::endl;
|
|
if (! this->export_project(&m_models[0], export_3mf_file, plate_data_list, project_presets, thumbnails, calibration_thumbnails, plate_bboxes, &m_print_config))
|
|
{
|
|
release_PlateData_list(plate_data_list);
|
|
flush_and_exit(CLI_EXPORT_3MF_ERROR);
|
|
}
|
|
release_PlateData_list(plate_data_list);
|
|
for (unsigned int i = 0; i < thumbnails.size(); i++)
|
|
delete thumbnails[i];
|
|
|
|
for (unsigned int i = 0; i < calibration_thumbnails.size(); i++)
|
|
delete calibration_thumbnails[i];
|
|
|
|
for (int i = 0; i < plate_bboxes.size(); i++)
|
|
delete plate_bboxes[i];
|
|
}
|
|
|
|
//BBS: release glfw
|
|
if (export_to_3mf) {
|
|
glfwTerminate();
|
|
}
|
|
|
|
#if defined(__linux__) || defined(__LINUX__)
|
|
if (g_cli_callback_mgr.is_started()) {
|
|
PrintBase::SlicingStatus slicing_status{100, "All done, Success"};
|
|
cli_status_callback(slicing_status);
|
|
}
|
|
|
|
g_cli_callback_mgr.stop();
|
|
#endif
|
|
|
|
//BBS: flush logs
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", Finished" << std::endl;
|
|
boost::nowide::cout.flush();
|
|
boost::nowide::cerr.flush();
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool CLI::setup(int argc, char **argv)
|
|
{
|
|
{
|
|
Slic3r::set_logging_level(1);
|
|
const char *loglevel = boost::nowide::getenv("BBL_LOGLEVEL");
|
|
if (loglevel != nullptr) {
|
|
if (loglevel[0] >= '0' && loglevel[0] <= '9' && loglevel[1] == 0)
|
|
set_logging_level(loglevel[0] - '0');
|
|
else
|
|
boost::nowide::cerr << "Invalid BBL_LOGLEVEL environment variable: " << loglevel << std::endl;
|
|
}
|
|
}
|
|
|
|
// Detect the operating system flavor after SLIC3R_LOGLEVEL is set.
|
|
detect_platform();
|
|
|
|
#ifdef WIN32
|
|
// Notify user that a blacklisted DLL was injected into BambuStudio process (for example Nahimic, see GH #5573).
|
|
// We hope that if a DLL is being injected into a BambuStudio process, it happens at the very start of the application,
|
|
// thus we shall detect them now.
|
|
if (BlacklistedLibraryCheck::get_instance().perform_check()) {
|
|
std::wstring text = L"Following DLLs have been injected into the BambuStudio process:\n\n";
|
|
text += BlacklistedLibraryCheck::get_instance().get_blacklisted_string();
|
|
text += L"\n\n"
|
|
L"BambuStudio is known to not run correctly with these DLLs injected. "
|
|
L"We suggest stopping or uninstalling these services if you experience "
|
|
L"crashes or unexpected behaviour while using BambuStudio.\n"
|
|
L"For example, ASUS Sonic Studio injects a Nahimic driver, which makes BambuStudio "
|
|
L"to crash on a secondary monitor";
|
|
MessageBoxW(NULL, text.c_str(), L"Warning"/*L"Incopatible library found"*/, MB_OK);
|
|
}
|
|
#endif
|
|
|
|
// See Invoking prusa-slicer from $PATH environment variable crashes #5542
|
|
// boost::filesystem::path path_to_binary = boost::filesystem::system_complete(argv[0]);
|
|
boost::filesystem::path path_to_binary = boost::dll::program_location();
|
|
|
|
// Path from the Slic3r binary to its resources.
|
|
#ifdef __APPLE__
|
|
// The application is packed in the .dmg archive as 'Slic3r.app/Contents/MacOS/Slic3r'
|
|
// The resources are packed to 'Slic3r.app/Contents/Resources'
|
|
boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path() / "../Resources";
|
|
#elif defined _WIN32
|
|
// The application is packed in the .zip archive in the root,
|
|
// The resources are packed to 'resources'
|
|
// Path from Slic3r binary to resources:
|
|
boost::filesystem::path path_resources = path_to_binary.parent_path() / "resources";
|
|
#elif defined SLIC3R_FHS
|
|
// The application is packaged according to the Linux Filesystem Hierarchy Standard
|
|
// Resources are set to the 'Architecture-independent (shared) data', typically /usr/share or /usr/local/share
|
|
boost::filesystem::path path_resources = SLIC3R_FHS_RESOURCES;
|
|
#else
|
|
// The application is packed in the .tar.bz archive (or in AppImage) as 'bin/slic3r',
|
|
// The resources are packed to 'resources'
|
|
// Path from Slic3r binary to resources:
|
|
boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path() / "../resources";
|
|
#endif
|
|
|
|
set_resources_dir(path_resources.string());
|
|
set_var_dir((path_resources / "images").string());
|
|
set_local_dir((path_resources / "i18n").string());
|
|
set_sys_shapes_dir((path_resources / "shapes").string());
|
|
|
|
// Parse all command line options into a DynamicConfig.
|
|
// If any option is unsupported, print usage and abort immediately.
|
|
t_config_option_keys opt_order;
|
|
if (! m_config.read_cli(argc, argv, &m_input_files, &opt_order)) {
|
|
// Separate error message reported by the CLI parser from the help.
|
|
boost::nowide::cerr << std::endl;
|
|
this->print_help();
|
|
return false;
|
|
}
|
|
// Parse actions and transform options.
|
|
for (auto const &opt_key : opt_order) {
|
|
if (cli_actions_config_def.has(opt_key))
|
|
m_actions.emplace_back(opt_key);
|
|
else if (cli_transform_config_def.has(opt_key))
|
|
m_transforms.emplace_back(opt_key);
|
|
}
|
|
|
|
#if !BBL_RELEASE_TO_PUBLIC
|
|
{
|
|
const ConfigOptionInt *opt_loglevel = m_config.opt<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 1
|
|
SET_DEFULTER_HANDLER();
|
|
#else
|
|
AddVectoredExceptionHandler(1, CBaseException::UnhandledExceptionFilter);
|
|
#endif
|
|
// Call the UTF8 main.
|
|
return CLI().run(argc, argv_ptrs.data());
|
|
}
|
|
}
|
|
#else /* _MSC_VER */
|
|
int main(int argc, char **argv)
|
|
{
|
|
return CLI().run(argc, argv);
|
|
}
|
|
#endif /* _MSC_VER */
|