mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-24 15:13:58 -06:00

1. first formal version of macos 2. add the bambu networking plugin install logic 3. auto compute the wipe volume when filament change 4. add the logic of wiping into support 5. refine the GUI layout and icons, improve the gui apperance in lots of small places 6. serveral improve to support 7. support AMS auto-mapping 8. disable lots of unstable features: such as params table, media file download, HMS 9. fix serveral kinds of bugs 10. update the document of building 11. ...
1746 lines
83 KiB
C++
1746 lines
83 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>
|
|
#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;
|
|
|
|
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
|
|
#define flush_and_exit(ret) { boost::nowide::cout << __FUNCTION__ << " found error, exit" << std::endl;\
|
|
boost::nowide::cout.flush();\
|
|
boost::nowide::cerr.flush();\
|
|
exit(ret);}
|
|
|
|
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("bambustudio_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 1;
|
|
}
|
|
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_studio\bamboo_slicer\build\src\Debug\bambu-studio.exe",
|
|
"--slice",
|
|
"0",
|
|
"--export-3mf=output.3mf",
|
|
"test_thumbnail.3mf",
|
|
"--load-settings",
|
|
"machine.json;process.json",
|
|
"--load-filaments",
|
|
"filament.json;filament.json;filament.json;filament.json"
|
|
};
|
|
if (! this->setup(debug_argc, debug_argv))*/
|
|
if (!this->setup(argc, argv))
|
|
{
|
|
boost::nowide::cerr << "setup params error" << std::endl;
|
|
return 1;
|
|
}
|
|
BOOST_LOG_TRIVIAL(info) << "finished setup params, argc="<< argc << std::endl;
|
|
std::string temp_path = wxStandardPaths::Get().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;
|
|
#ifdef SLIC3R_GUI
|
|
/*#if !defined(_WIN32) && !defined(__APPLE__)
|
|
// likely some linux / unix system
|
|
const char *display = boost::nowide::getenv("DISPLAY");
|
|
// const char *wayland_display = boost::nowide::getenv("WAYLAND_DISPLAY");
|
|
//if (! ((display && *display) || (wayland_display && *wayland_display))) {
|
|
if (! (display && *display)) {
|
|
// DISPLAY not set.
|
|
boost::nowide::cerr << "DISPLAY not set, GUI mode not available." << std::endl << std::endl;
|
|
this->print_help(false);
|
|
// Indicate an error.
|
|
return 1;
|
|
}
|
|
#endif // some linux / unix system*/
|
|
Slic3r::GUI::GUI_InitParams params;
|
|
params.argc = argc;
|
|
params.argv = argv;
|
|
params.load_configs = load_configs;
|
|
params.extra_config = std::move(m_extra_config);
|
|
params.input_files = std::move(m_input_files);
|
|
//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(1);
|
|
}
|
|
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(1);
|
|
}
|
|
//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(1);
|
|
}
|
|
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(1);
|
|
}
|
|
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(1);
|
|
}
|
|
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(1);
|
|
}
|
|
//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(1);
|
|
}
|
|
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(1);
|
|
}
|
|
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(1);
|
|
}
|
|
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(1);
|
|
}
|
|
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(1);
|
|
}
|
|
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(1);
|
|
}
|
|
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(1);
|
|
}
|
|
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(1);
|
|
}
|
|
if (model.objects.empty()) {
|
|
boost::nowide::cerr << "Error: file is empty: " << file << std::endl;
|
|
continue;
|
|
}
|
|
m_models.push_back(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(1);
|
|
/*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(1);
|
|
}
|
|
|
|
//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(1);
|
|
}
|
|
}
|
|
} 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(1);
|
|
}
|
|
for (auto &model : m_models)
|
|
for (auto &o : model.objects)
|
|
// this affects volumes:
|
|
o->scale_to_fit(opt);
|
|
} else if (opt_key == "cut" || opt_key == "cut_x" || opt_key == "cut_y") {
|
|
std::vector<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(1);
|
|
}
|
|
}
|
|
|
|
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 == "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(1);
|
|
} else if (opt_key == "export_obj") {
|
|
for (auto &model : m_models)
|
|
model.add_default_instances();
|
|
if (! this->export_models(IO::OBJ))
|
|
flush_and_exit(1);
|
|
}/* 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);
|
|
// 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(1);
|
|
}
|
|
else if (!warning.string.empty())
|
|
BOOST_LOG_TRIVIAL(info) << "got warnings: "<< warning.string << std::endl;
|
|
|
|
if (print->empty()) {
|
|
BOOST_LOG_TRIVIAL(info) << "Nothing to print for " << outfile << " . Either the print is empty or no object is fully inside the print volume." << std::endl;
|
|
flush_and_exit(1);
|
|
}
|
|
else
|
|
try {
|
|
std::string outfile_final;
|
|
BOOST_LOG_TRIVIAL(info) << "start Print::process for partplate "<<index << std::endl;
|
|
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);
|
|
} catch (const std::exception &ex) {
|
|
BOOST_LOG_TRIVIAL(info) << "found slicing or export error for partplate "<<index << std::endl;
|
|
boost::nowide::cerr << ex.what() << std::endl;
|
|
//continue;
|
|
flush_and_exit(1);
|
|
}
|
|
}//end for partplate
|
|
/*
|
|
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(1);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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(1);
|
|
}
|
|
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();
|
|
}
|
|
|
|
//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, see BambuStudio github issue #5573";
|
|
// MessageBoxW(NULL, text.c_str(), L"Warning"/*L"Incopatible library found"*/, MB_OK);
|
|
//}
|
|
#endif
|
|
|
|
// See Invoking prusa-slicer from $PATH environment variable crashes #5542
|
|
// boost::filesystem::path path_to_binary = boost::filesystem::system_complete(argv[0]);
|
|
boost::filesystem::path path_to_binary = boost::dll::program_location();
|
|
|
|
// Path from the Slic3r binary to its resources.
|
|
#ifdef __APPLE__
|
|
// The application is packed in the .dmg archive as 'Slic3r.app/Contents/MacOS/Slic3r'
|
|
// The resources are packed to 'Slic3r.app/Contents/Resources'
|
|
boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path() / "../Resources";
|
|
#elif defined _WIN32
|
|
// The application is packed in the .zip archive in the root,
|
|
// The resources are packed to 'resources'
|
|
// Path from Slic3r binary to resources:
|
|
boost::filesystem::path path_resources = path_to_binary.parent_path() / "resources";
|
|
#elif defined SLIC3R_FHS
|
|
// The application is packaged according to the Linux Filesystem Hierarchy Standard
|
|
// Resources are set to the 'Architecture-independent (shared) data', typically /usr/share or /usr/local/share
|
|
boost::filesystem::path path_resources = SLIC3R_FHS_RESOURCES;
|
|
#else
|
|
// The application is packed in the .tar.bz archive (or in AppImage) as 'bin/slic3r',
|
|
// The resources are packed to 'resources'
|
|
// Path from Slic3r binary to resources:
|
|
boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path() / "../resources";
|
|
#endif
|
|
|
|
set_resources_dir(path_resources.string());
|
|
set_var_dir((path_resources / "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_BUILD_ID << ":"
|
|
<< 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 = store_params.strategy|SaveStrategy::WithGcode;
|
|
|
|
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 bambustudio_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 */
|