#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/Time.hpp"
#include "libslic3r/Thread.hpp"
#include "libslic3r/BlacklistedLibraryCheck.hpp"

#include "libslic3r/Orient.hpp"

#include "OrcaSlicer.hpp"
//BBS: add exception handler for win32
#include <wx/stdpaths.h>
#ifdef WIN32
#include "BaseException.h"
#endif
#include "slic3r/GUI/PartPlate.hpp"
#include "slic3r/GUI/BitmapCache.hpp"
#include "slic3r/GUI/OpenGLManager.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/Camera.hpp"
#include <GLFW/glfw3.h>

#ifdef __WXGTK__
#include <X11/Xlib.h>
#endif

#ifdef SLIC3R_GUI
    #include "slic3r/GUI/GUI_Init.hpp"
#endif /* SLIC3R_GUI */

using namespace Slic3r;

/*typedef struct _error_message{
    int code;
    std::string message;
}error_message;*/


std::map<int, std::string> cli_errors = {
    {CLI_SUCCESS, "Success"},
    {CLI_ENVIRONMENT_ERROR, "Environment setup failed"},
    {CLI_INVALID_PARAMS, "Input param invalid"},
    {CLI_FILE_NOTFOUND, "Input file not found"},
    {CLI_FILELIST_INVALID_ORDER, "File list order invalid(please make sure 3mf in the first place)"},
    {CLI_CONFIG_FILE_ERROR, "Invalid config file, could not be parsed"},
    {CLI_DATA_FILE_ERROR, "Invalid model file, could not be loaded"},
    {CLI_INVALID_PRINTER_TECH, "Invalid printer technoledge"},
    {CLI_UNSUPPORTED_OPERATION, "Unsupported operation"},
    {CLI_COPY_OBJECTS_ERROR, "Copy objects error"},
    {CLI_SCALE_TO_FIT_ERROR, "Scale to fit error"},
    {CLI_EXPORT_STL_ERROR, "Export stl error"},
    {CLI_EXPORT_OBJ_ERROR, "Export obj error"},
    {CLI_EXPORT_3MF_ERROR, "Export 3mf error"},
    {CLI_OUT_OF_MEMORY, "Out of memory"},
    {CLI_NO_SUITABLE_OBJECTS, "Found no objects in print volume to slice"},
    {CLI_VALIDATE_ERROR, "Validate print error"},
    {CLI_OBJECTS_PARTLY_INSIDE, "Objects partly inside"},
    {CLI_EXPORT_CACHE_DIRECTORY_CREATE_FAILED, "Objects partly inside"},
    {CLI_EXPORT_CACHE_WRITE_FAILED, "export cached slicedata failed"},
    {CLI_IMPORT_CACHE_NOT_FOUND, "cached slicedata can not be found"},
    {CLI_IMPORT_CACHE_DATA_CAN_NOT_USE, "cached slicedata can not be used"},
    {CLI_IMPORT_CACHE_LOAD_FAILED, "load cached slicedata failed"},
    {CLI_SLICING_ERROR, "Slice error"}
};

#if defined(__linux__) || defined(__LINUX__)
#define PIPE_BUFFER_SIZE 512

typedef struct _cli_callback_mgr {
    int                 m_plate_count {0};
    int                 m_plate_index {0};
    int                 m_progress { 0 };
    int                 m_total_progress { 0 };
    std::string         m_message;
    int                 m_warning_step;
    bool                m_exit {false};
    bool                m_data_ready {false};
    bool                m_started {false};
    boost::thread               m_thread;
    // Mutex and condition variable to synchronize m_thread with the UI thread.
    std::mutex                  m_mutex;
    std::condition_variable     m_condition;
    int                 m_pipe_fd{-1};

    bool    is_started()
    {
        bool result;
        std::unique_lock<std::mutex> lck(m_mutex);
        result = m_started;
        lck.unlock();

        return result;
    }

    void set_plate_info(int index, int count)
    {
        BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": index="<<index<< ", count = "<< count;
        std::unique_lock<std::mutex> lck(m_mutex);
        m_plate_count = count;
        m_plate_index = index;
        m_progress = 0;
        lck.unlock();

        return;
    }

    void    notify()
    {
        if (m_pipe_fd < 0)
            return;

        json j;
        //record the headers
        j["plate_index"] = m_plate_index;
        j["plate_count"] = m_plate_count;
        j["plate_percent"] = m_progress;
        j["total_percent"] = m_total_progress;
        if (m_warning_step >= 0)
            j["warning"] = m_message;
        else
            j["message"] = m_message;

        std::string notify_message = j.dump();
        //notify_message = "Plate "+ std::to_string(m_plate_index) + "/" +std::to_string(m_plate_count)+  ": Percent " + std::to_string(m_progress) + ": "+m_message;

        char pipe_message[PIPE_BUFFER_SIZE] = {0};
        sprintf(pipe_message, "%s\n", notify_message.c_str());

        int ret = write(m_pipe_fd, pipe_message, strlen(pipe_message));
        BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ": write returns "<<ret;

        return;
    }

    void    thread_proc()
    {
        std::unique_lock<std::mutex> lck(m_mutex);
        m_started = true;
        m_data_ready = false;
        lck.unlock();
        m_condition.notify_one();
        BOOST_LOG_TRIVIAL(info) << "cli_callback_mgr_t::thread_proc started.";
        while(1) {
            lck.lock();
            m_condition.wait(lck, [this](){ return m_data_ready || m_exit; });
            BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ": wakup.";
            if (m_data_ready) {
                notify();
                m_data_ready = false;
            }
            if (m_exit) {
                BOOST_LOG_TRIVIAL(info) << "cli_callback_mgr_t::thread_proc will exit.";
                break;
            }
            lck.unlock();
            m_condition.notify_one();
        }
        lck.unlock();
        BOOST_LOG_TRIVIAL(info) << "cli_callback_mgr_t::thread_proc exit.";
    }

    void    update(int percent, std::string message, int warning_step)
    {
        std::unique_lock<std::mutex> lck(m_mutex);
        if (!m_started) {
            lck.unlock();
            return;
        }

        if ((m_progress >= percent)&&(warning_step == -1)) {
            //already update before
            lck.unlock();
            return;
        }
        int old_total_progress = m_total_progress;
        BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": percent="<<percent<< ", warning_step=" << warning_step << ", plate_index = "<< m_plate_index<<", plate_count="<< m_plate_count<<", message="<<message;
        if (warning_step == -1) {
            m_progress = percent;
            if ((m_plate_count <= 1) && (m_plate_index >= 1))
                m_total_progress = 3 + 0.9*m_progress;
            else if ((m_plate_count > 1) && (m_plate_index >= 1)) {
                m_total_progress = 3 + ((float)(m_plate_index - 1)*90)/m_plate_count + ((float)m_progress*0.9)/m_plate_count;
            }
            else
                m_total_progress = m_progress;
        }
        if (m_total_progress < old_total_progress)
            m_total_progress = old_total_progress;
        m_message = message;
        m_warning_step = warning_step;
        m_data_ready = true;
        lck.unlock();
        m_condition.notify_one();
        BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": m_total_progress="<<m_total_progress;
        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();\
    for (Model &model : m_models) {\
       model.remove_backup_path_if_exist();\
    }\
    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();\
    for (Model &model : m_models) {\
       model.remove_backup_path_if_exist();\
    }\
    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;
}

const float bed3d_ax3s_default_stem_radius = 0.5f;
const float bed3d_ax3s_default_stem_length = 25.0f;
const float bed3d_ax3s_default_tip_radius = 2.5f * bed3d_ax3s_default_stem_radius;
const float bed3d_ax3s_default_tip_length = 5.0f;

int CLI::run(int argc, char **argv)
{
    // Mark the main thread for the debugger and for runtime checks.
    set_current_thread_name("orcaslicer_main");
    // Save the thread ID of the main thread.
    save_main_thread_id();
#ifdef __WXGTK__
    // On Linux, wxGTK has no support for Wayland, and the app crashes on
    // startup if gtk3 is used. This env var has to be set explicitly to
    // instruct the window manager to fall back to X server mode.
    ::setenv("GDK_BACKEND", "x11", /* replace */ true);

    // Also on Linux, we need to tell Xlib that we will be using threads,
    // lest we crash when we fire up GStreamer.
    XInitThreads();
#endif

	// Switch boost::filesystem to utf8.
    try {
        boost::nowide::nowide_filesystem();
    } catch (const std::runtime_error& ex) {
        std::string caption = std::string(SLIC3R_APP_FULL_NAME) + " Error";
        std::string text = std::string("boost::nowide::nowide_filesystem Failed!\n") + (
        	SLIC3R_APP_FULL_NAME " will now terminate.\n\n") + ex.what();
    #if defined(_WIN32) && defined(SLIC3R_GUI)
        if (m_actions.empty())
        	// Empty actions means Slicer is executed in the GUI mode. Show a GUI message.
            MessageBoxA(NULL, text.c_str(), caption.c_str(), MB_OK | MB_ICONERROR);
    #endif
        boost::nowide::cerr << text.c_str() << std::endl;
        return CLI_ENVIRONMENT_ERROR;
    }
    BOOST_LOG_TRIVIAL(info) << "Current OraSlicer Version "<< SoftFever_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 = 7;
    char *debug_argv[] = {
        "E:\work\projects\bambu_release\bamboo_slicer\build_debug\src\Debug\orca-slicer.exe",
        "--slice",
        "9",
        //"--load-settings",
        //"machine.json;process.json",
        "--load-slicedata",
        "cached_data",
        "--export-3mf=output.3mf",
        "--slice",
        "0",
        "Cube.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*/

    bool translate_old = false, regenerate_thumbnails = false;
    int current_width, current_depth, current_height;
    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;
    const std::vector<int>                      &skip_objects             = m_config.option<ConfigOptionInts>("skip_objects", true)->values;
    bool   need_skip      = (skip_objects.size() > 0)?true:false;

    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 OrcaSlicer 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
    }
    else {
        const ConfigOptionInt *opt_loglevel = m_config.opt<ConfigOptionInt>("debug");
        if (opt_loglevel) {
            set_logging_level(opt_loglevel->value);
        }
        else {
            set_logging_level(2);
        }
    }

    BOOST_LOG_TRIVIAL(info) << "start_gui="<< start_gui << std::endl;

    //BBS: add plate data related logic
    PlateDataPtrs plate_data_src;
    int arrange_option;
    int plate_to_slice = 0, filament_count = 0;
    bool first_file = true, is_bbl_3mf = false, need_arrange = true, has_thumbnails = false, up_config_to_date = false, normative_check = true;
    Semver file_version;
    std::map<size_t, bool> orients_requirement;
    std::vector<Preset*> project_presets;
    std::string new_printer_name, current_printer_name, new_process_name, current_process_name, current_printer_system_name, current_process_system_name;//, printer_inherits, print_inherits;
    std::vector<std::string> upward_compatible_printers, new_print_compatible_printers, current_print_compatible_printers, current_different_settings;
    std::vector<std::string> current_filaments_name, current_filaments_system_name, current_inherits_group;
    DynamicPrintConfig load_process_config, load_machine_config;
    std::string pipe_name;

    // Read input file(s) if any.
    BOOST_LOG_TRIVIAL(info) << "Will start to read model file now, file count :" << m_input_files.size() << "\n";
    ConfigOptionInt* slice_option = m_config.option<ConfigOptionInt>("slice");
    if (slice_option)
        plate_to_slice = slice_option->value;

    ConfigOptionBool* normative_check_option = m_config.option<ConfigOptionBool>("normative_check");
    if (normative_check_option)
        normative_check = normative_check_option->value;

    ConfigOptionBool* uptodate_option = m_config.option<ConfigOptionBool>("uptodate");
    if (uptodate_option)
        up_config_to_date = uptodate_option->value;

    ConfigOptionString* pipe_option = m_config.option<ConfigOptionString>("pipe");
    if (pipe_option) {
        pipe_name = pipe_option->value;
        if (!pipe_name.empty()) {
            BOOST_LOG_TRIVIAL(info) << boost::format("Will use pipe %1%")%pipe_name;
#if defined(__linux__) || defined(__LINUX__)
            g_cli_callback_mgr.start(pipe_name);
#endif
        }
    }

    /*for (const std::string& file : m_input_files)
        if (is_gcode_file(file) && boost::filesystem::exists(file)) {
            start_as_gcodeviewer = true;
            BOOST_LOG_TRIVIAL(info) << "found a gcode file:" << file << ", will start as gcode viewer\n";
            break;
        }*/
    BOOST_LOG_TRIVIAL(info) << boost::format("plate_to_slice=%1%, normative_check=%2%")%plate_to_slice %normative_check;
    //if (!start_as_gcodeviewer) {
        for (const std::string& file : m_input_files) {
            if (!boost::filesystem::exists(file)) {
                boost::nowide::cerr << "No such file: " << file << std::endl;
                flush_and_exit(CLI_FILE_NOTFOUND);
            }
            Model model;
            //BBS: add plate related logic
            //bool load_aux = false;
            BOOST_LOG_TRIVIAL(info) << "read model file:" << file << "\n";
            try {
                // When loading an AMF or 3MF, config is imported as well, including the printer technology.
                DynamicPrintConfig config;
                ConfigSubstitutionContext config_substitutions(config_substitution_rule);

                //FIXME should we check the version here? // | LoadStrategy::CheckVersion ?
                is_bbl_3mf = false;
                LoadStrategy strategy;
                if (boost::algorithm::iends_with(file, ".3mf") && first_file) {
                    strategy = LoadStrategy::LoadModel | LoadStrategy::LoadConfig|LoadStrategy::AddDefaultInstances | LoadStrategy::LoadAuxiliary;
                    //load_aux = true;
                }
                else
                    strategy = LoadStrategy::LoadModel | LoadStrategy::AddDefaultInstances;
                // BBS: adjust whebackup
                //LoadStrategy strategy = LoadStrategy::LoadModel | LoadStrategy::LoadConfig|LoadStrategy::AddDefaultInstances;
                //if (load_aux) strategy = strategy | LoadStrategy::LoadAuxiliary;
                model = Model::read_from_file(file, &config, &config_substitutions, strategy, &plate_data_src, &project_presets, &is_bbl_3mf, &file_version, nullptr, nullptr, nullptr, nullptr, nullptr, plate_to_slice);
                if (is_bbl_3mf)
                {
                    if (!first_file)
                    {
                        BOOST_LOG_TRIVIAL(info) << "The BBL 3mf file should be placed at the first position, filename=" << file << "\n";
                        flush_and_exit(CLI_FILELIST_INVALID_ORDER);
                    }
                    BOOST_LOG_TRIVIAL(info) << "the first file is a 3mf, got plate count:" << plate_data_src.size() << "\n";
                    need_arrange = false;
                    for (ModelObject* o : model.objects)
                    {
                        orients_requirement.insert(std::pair<size_t, bool>(o->id().id, false));
                        BOOST_LOG_TRIVIAL(info) << "object "<<o->name <<", id :" << o->id().id << ", from bbl 3mf\n";
                    }

                    Semver old_version(1, 5, 9), old_version2(1, 5, 9);
                    if ((file_version < old_version) && !config.empty()) {
                        translate_old = true;
                        BOOST_LOG_TRIVIAL(info) << boost::format("old 3mf version %1%, need to translate")%file_version.to_string();
                    }
                    if ((file_version < old_version2) && !config.empty()) {
                        regenerate_thumbnails = true;
                        BOOST_LOG_TRIVIAL(info) << boost::format("old 3mf version %1%, need to regenerate_thumbnails for all")%file_version.to_string();
                    }

                    if (normative_check) {
                        ConfigOptionStrings* postprocess_scripts = config.option<ConfigOptionStrings>("post_process");
                        if (postprocess_scripts) {
                            std::vector<std::string> postprocess_values = postprocess_scripts->values;
                            if (postprocess_values.size() > 0) {
                                BOOST_LOG_TRIVIAL(error) << boost::format("normative_check: postprocess not supported, array size %1%")%postprocess_values.size();
                                flush_and_exit(CLI_POSTPROCESS_NOT_SUPPORTED);
                            }
                        }
                    }

                    /*for (ModelObject *model_object : model.objects)
                        for (ModelInstance *model_instance : model_object->instances)
                        {
                            const Vec3d &instance_offset = model_instance->get_offset();
                            BOOST_LOG_TRIVIAL(info) << boost::format("instance %1% transform {%2%,%3%,%4%} at %5%:%6%")% model_object->name % instance_offset.x() % instance_offset.y() %instance_offset.z() % __FUNCTION__ % __LINE__<< std::endl;
                        }*/
                    current_printer_name = config.option<ConfigOptionString>("printer_settings_id")->value;
                    current_process_name = config.option<ConfigOptionString>("print_settings_id")->value;
                    current_filaments_name = config.option<ConfigOptionStrings>("filament_settings_id")->values;
                    ConfigOptionStrings* option_strings = config.option<ConfigOptionStrings>("inherits_group");
                    if (option_strings) {
                        current_inherits_group = option_strings->values;
                        size_t size = current_inherits_group.size();
                        if (current_inherits_group[size-1].empty())
                            current_printer_system_name = current_printer_name;
                        else
                            current_printer_system_name = current_inherits_group[size-1];

                        if (current_inherits_group[0].empty())
                            current_process_system_name = current_process_name;
                        else
                            current_process_system_name = current_inherits_group[0];

                        current_filaments_system_name.resize(size - 2);
                        for (int index = 1; index < (size - 1); index++) {
                            if (current_inherits_group[index].empty())
                                current_filaments_system_name[index-1] = current_filaments_name[index-1];
                            else
                                current_filaments_system_name[index-1] = current_inherits_group[index];
                        }
                    }
                    else {
                        current_printer_system_name = current_printer_name;
                        current_process_system_name = current_process_name;
                        current_filaments_system_name = current_filaments_name;
                    }
                    filament_count = current_filaments_name.size();
                    upward_compatible_printers = config.option<ConfigOptionStrings>("upward_compatible_machine", true)->values;
                    current_print_compatible_printers  = config.option<ConfigOptionStrings>("print_compatible_printers", true)->values;
                    current_different_settings = config.option<ConfigOptionStrings>("different_settings_to_system", true)->values;
                }
                else
                {
                    need_arrange = true;
                    for (ModelObject* o : model.objects)
                    {
                        orients_requirement.insert(std::pair<size_t, bool>(o->id().id, true));
                        BOOST_LOG_TRIVIAL(info) << "object "<<o->name <<", id :"  << o->id().id << ", from stl or other 3mf\n";
                        o->ensure_on_bed();
                    }
                }
                first_file = false;

                PrinterTechnology other_printer_technology = get_printer_technology(config);
                if (printer_technology == ptUnknown) {
                    printer_technology = other_printer_technology;
                }
                if ((printer_technology != other_printer_technology) && (other_printer_technology != ptUnknown)) {
                    boost::nowide::cerr << "invalid printer_technology " <<printer_technology<<", from source file "<< file <<std::endl;
                    flush_and_exit(CLI_INVALID_PRINTER_TECH);
                }
                if (!config_substitutions.substitutions.empty()) {
                    BOOST_LOG_TRIVIAL(info) << "Found legacy configuration values, substituted when loading " << file << ":\n";
                    for (const ConfigSubstitution &subst : config_substitutions.substitutions)
                        BOOST_LOG_TRIVIAL(info) << "\tkey = \"" << subst.opt_def->opt_key << "\"\t old_value = \"" << subst.old_value << "\tnew_value = \"" << subst.new_value->serialize() << "\"\n";
                }

                // config is applied to m_print_config before the current m_config values.
                config += std::move(m_print_config);
                m_print_config = std::move(config);
            }
            catch (std::exception& e) {
                boost::nowide::cerr << file << ": " << e.what() << std::endl;
                flush_and_exit(CLI_DATA_FILE_ERROR);
            }
            if (model.objects.empty()) {
                boost::nowide::cerr << "Error: file is empty: " << file << std::endl;
                continue;
            }
            m_models.push_back(std::move(model));
        }
    //}

    auto load_system_config_file = [config_substitution_rule](const std::string& file, DynamicPrintConfig& config, std::string& config_type,
                                std::string& config_name, std::string& filament_id) {
        if (! boost::filesystem::exists(file)) {
            boost::nowide::cerr << __FUNCTION__<< ": can not find setting file: " << file << std::endl;
            return CLI_FILE_NOTFOUND;
        }
        ConfigSubstitutions config_substitutions;
        try {
            BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ":load setting file "<< file << ", with rule "<< config_substitution_rule << std::endl;
            std::map<std::string, std::string> key_values;
            std::string reason, config_from;

            config_substitutions = config.load_from_json(file, config_substitution_rule, key_values, reason);
            if (!reason.empty()) {
                BOOST_LOG_TRIVIAL(error) <<__FUNCTION__<<  ":Can not load config from file "<<file<<"\n";
                return CLI_CONFIG_FILE_ERROR;
            }

            config_name = key_values[BBL_JSON_KEY_NAME];
            auto from_iter = key_values.find(BBL_JSON_KEY_FROM);
            if (from_iter != key_values.end()) {
                config_from = from_iter->second;
            }
            if (config_from != "system") {
                boost::nowide::cerr <<__FUNCTION__ << boost::format(":file %1%'s from %2% unsupported") % file % config_from;
                return CLI_CONFIG_FILE_ERROR;
            }

            auto type_iter = key_values.find(BBL_JSON_KEY_TYPE);
            if (type_iter != key_values.end()) {
                config_type = type_iter->second;
            }
            if (config_type == "machine") {
                //config.set("printer_settings_id", config_name, true);
                //printer_inherits = config.option<ConfigOptionString>("inherits", true)->value;
            }
            else if (config_type == "process") {
                //config.set("print_settings_id", config_name, true);
                //print_inherits = config.option<ConfigOptionString>("inherits", true)->value;
            }
            else if (config_type == "filament") {
                auto filament_id_iter = key_values.find(BBL_JSON_KEY_FILAMENT_ID);
                if (filament_id_iter != key_values.end())
                    filament_id = filament_id_iter->second;
            }
            else {
                boost::nowide::cerr <<__FUNCTION__ << boost::format(": unknown config type %1% of file %2% in load-settings") % config_type % file;
                return CLI_CONFIG_FILE_ERROR;
            }
            config.normalize_fdm();

            if (! config_substitutions.empty()) {
                BOOST_LOG_TRIVIAL(info) << "Found legacy configuration values, substituted when loading " << file << ":\n";
                for (const ConfigSubstitution &subst : config_substitutions)
                    BOOST_LOG_TRIVIAL(info) << "\tkey = \"" << subst.opt_def->opt_key << "\"\t old_value = \"" << subst.old_value << "\tnew_value = \"" << subst.new_value->serialize() << "\"\n";
            }
            else {
                BOOST_LOG_TRIVIAL(info) << "no substitutions performed from file " << file << "\n";
            }
            //config.erase("inherits");
            //config.erase("compatible_printers");
            //BOOST_LOG_TRIVIAL(info) << "got printable_area "<< config.option("printable_area")->serialize() << std::endl;
        } catch (std::exception &ex) {
            boost::nowide::cerr << __FUNCTION__<< ":Loading setting file \"" << file << "\" failed: " << ex.what() << std::endl;
            return CLI_CONFIG_FILE_ERROR;
        }
        return 0;
    };
    BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ":before load settings, file count="<< load_configs.size() << std::endl;
    //std::vector<std::string> filament_compatible_printers;
    // load config files supplied via --load
    for (auto const &file : load_configs) {
        DynamicPrintConfig  config;
        std::string config_type, config_name, filament_id;
        int ret = load_system_config_file(file, config, config_type, config_name, filament_id);
        if (ret) {
            flush_and_exit(ret);
        }

        if (config_type == "machine") {
            if (!new_printer_name.empty()) {
                boost::nowide::cerr << "duplicate machine config file: " << file << std::endl;
                flush_and_exit(CLI_CONFIG_FILE_ERROR);
            }
            new_printer_name = config_name;
            config.set("printer_settings_id", new_printer_name, true);
            //printer_inherits = config.option<ConfigOptionString>("inherits", true)->value;
            load_machine_config = std::move(config);
        }
        else if (config_type == "process") {
            if (!new_process_name.empty()) {
                boost::nowide::cerr << "duplicate process config file: " << file << std::endl;
                flush_and_exit(CLI_CONFIG_FILE_ERROR);
            }
            new_process_name = config_name;
            config.set("print_settings_id", new_process_name, true);
            //print_inherits = config.option<ConfigOptionString>("inherits", true)->value;
            new_print_compatible_printers = config.option<ConfigOptionStrings>("compatible_printers", true)->values;
            load_process_config = std::move(config);
        }

        PrinterTechnology other_printer_technology = get_printer_technology(config);
        if (printer_technology == ptUnknown) {
            printer_technology = other_printer_technology;
        }

        if ((printer_technology != other_printer_technology)&&(other_printer_technology != ptUnknown)){
            boost::nowide::cerr << "invalid printer_technology " <<printer_technology<<", from config "<< file <<std::endl;
            flush_and_exit(CLI_INVALID_PRINTER_TECH);
        }
    }

    //load filaments files
    int load_filament_count = load_filaments.size();
    std::vector<int> load_filaments_index;
    std::vector<DynamicPrintConfig> load_filaments_config;
    std::vector<std::string> load_filaments_id;
    std::vector<std::string> load_filaments_name;
    int current_index = 0;
    for (int index = 0; index < load_filament_count; index++) {
        const std::string& file = load_filaments[index];
        current_index++;
        if (!file.empty()) {
            DynamicPrintConfig  config;
            std::string config_type, config_name, filament_id;
            int ret = load_system_config_file(file, config, config_type, config_name, filament_id);
            if (ret) {
                flush_and_exit(ret);
            }

            if (config_type != "filament") {
                boost::nowide::cerr <<__FUNCTION__ << boost::format(": unknown config type %1% of file %2% in load-filaments") % config_type % file;
                flush_and_exit(CLI_CONFIG_FILE_ERROR);
            }

            PrinterTechnology other_printer_technology = get_printer_technology(config);
            if (printer_technology == ptUnknown) {
                printer_technology = other_printer_technology;
            }
            if ((printer_technology != other_printer_technology) && (other_printer_technology != ptUnknown)) {
                boost::nowide::cerr << "invalid printer_technology " <<printer_technology<<", from filament file "<< file <<std::endl;
                flush_and_exit(CLI_INVALID_PRINTER_TECH);
            }
            load_filaments_id.push_back(filament_id);
            load_filaments_name.push_back(config_name);
            load_filaments_config.push_back(std::move(config));
            load_filaments_index.push_back(current_index);
        }
        else {
            continue;
        }
    }

    if (filament_count == 0)
        filament_count = load_filament_count;

    //load system config if needed
    bool fetch_compatible_values = false, fetch_upward_values = false;
    if (is_bbl_3mf && up_config_to_date) {
        if (new_printer_name.empty() && !current_printer_system_name.empty()) {
            //use the original printer name in 3mf
            std::string system_printer_path = resources_dir() + "/profiles/BBL/machine_full/"+current_printer_system_name+".json";
            if (! boost::filesystem::exists(system_printer_path)) {
                boost::nowide::cerr << __FUNCTION__<< ": can not find system preset file: " << system_printer_path << std::endl;
                //use original one
            }
            else {
                DynamicPrintConfig  config;
                std::string config_type, config_name, filament_id;
                int ret = load_system_config_file(system_printer_path, config, config_type, config_name, filament_id);
                if (ret)
                    flush_and_exit(ret);
                upward_compatible_printers = config.option<ConfigOptionStrings>("upward_compatible_machine", true)->values;
                config.set("printer_settings_id", config_name, true);
                load_machine_config = std::move(config);
            }
        }
        else
            fetch_upward_values = true;

        if (new_process_name.empty() && !current_process_system_name.empty()) {
            //use the original printer name in 3mf
            std::string system_process_path = resources_dir() + "/profiles/BBL/process_full/"+current_process_system_name+".json";
            if (! boost::filesystem::exists(system_process_path)) {
                boost::nowide::cerr << __FUNCTION__<< ": can not find system preset file: " << system_process_path << std::endl;
                //use original one
            }
            else {
                DynamicPrintConfig  config;
                std::string config_type, config_name, filament_id;
                int ret = load_system_config_file(system_process_path, config, config_type, config_name, filament_id);
                if (ret)
                    flush_and_exit(ret);
                current_print_compatible_printers  = config.option<ConfigOptionStrings>("compatible_printers", true)->values;
                config.set("print_settings_id", config_name, true);
                load_process_config = std::move(config);
            }
        }
        else
            fetch_compatible_values = true;

        if (load_filaments_config.empty() && !current_filaments_system_name.empty()) {
            for (int index = 0; index < current_filaments_system_name.size(); index++) {
                std::string system_filament_path = resources_dir() + "/profiles/BBL/filament_full/"+current_filaments_system_name[index]+".json";
                current_index++;
                if (! boost::filesystem::exists(system_filament_path)) {
                    boost::nowide::cerr << __FUNCTION__<< ": can not find system preset file: " << system_filament_path << std::endl;
                    continue;
                }
                DynamicPrintConfig  config;
                std::string config_type, config_name, filament_id;
                int ret = load_system_config_file(system_filament_path, config, config_type, config_name, filament_id);
                if (ret)
                    flush_and_exit(ret);

                load_filaments_id.push_back(filament_id);
                load_filaments_name.push_back(config_name);
                load_filaments_config.push_back(std::move(config));
                load_filaments_index.push_back(current_index);
            }
        }
    }
    else if (is_bbl_3mf){
        fetch_upward_values = true;
        fetch_compatible_values = true;
    }

    //fetch upward_compatible_machine
    if (fetch_upward_values) {
        if (!current_printer_system_name.empty()) {
            //use the original printer name in 3mf
            std::string system_printer_path = resources_dir() + "/profiles/BBL/machine_full/"+current_printer_system_name+".json";
            if (! boost::filesystem::exists(system_printer_path)) {
                boost::nowide::cerr << __FUNCTION__<< ": can not find system preset file: " << system_printer_path << std::endl;
                //skip
            }
            else {
                DynamicPrintConfig  config;
                std::string config_type, config_name, filament_id;
                int ret = load_system_config_file(system_printer_path, config, config_type, config_name, filament_id);
                if (ret)
                    flush_and_exit(ret);
                upward_compatible_printers = config.option<ConfigOptionStrings>("upward_compatible_machine", true)->values;
            }
        }
    }

    //fetch print_compatible_printers
    if (fetch_compatible_values) {
        if (!current_process_system_name.empty()) {
            //use the original printer name in 3mf
            std::string system_process_path = resources_dir() + "/profiles/BBL/process_full/"+current_process_system_name+".json";
            if (! boost::filesystem::exists(system_process_path)) {
                boost::nowide::cerr << __FUNCTION__<< ": can not find system preset file: " << system_process_path << std::endl;
                //use original one
            }
            else {
                DynamicPrintConfig  config;
                std::string config_type, config_name, filament_id;
                int ret = load_system_config_file(system_process_path, config, config_type, config_name, filament_id);
                if (ret)
                    flush_and_exit(ret);
                current_print_compatible_printers  = config.option<ConfigOptionStrings>("compatible_printers", true)->values;
            }
        }
    }

    //upwards check
    bool process_compatible = true, machine_upwards = false;
    if (!new_printer_name.empty()) {
        process_compatible = false;
        if (!new_process_name.empty()) {
            for (int index = 0; index < new_print_compatible_printers.size(); index++) {
                if (new_print_compatible_printers[index] == new_printer_name) {
                    process_compatible = true;
                    break;
                }
            }
            BOOST_LOG_TRIVIAL(info) << boost::format("new printer %1%, new process %2%, compatible %3%")%new_printer_name %new_process_name %process_compatible;
        }
        else {
            for (int index = 0; index < current_print_compatible_printers.size(); index++) {
                if (current_print_compatible_printers[index] == new_printer_name) {
                    process_compatible = true;
                    break;
                }
            }
            BOOST_LOG_TRIVIAL(info) << boost::format("new printer %1%, old process %2%, compatible %3%")%new_printer_name %current_process_name %process_compatible;
        }
    }
    else if (!new_process_name.empty()) {
        process_compatible = false;
        for (int index = 0; index < new_print_compatible_printers.size(); index++) {
            if (new_print_compatible_printers[index] == current_printer_name) {
                process_compatible = true;
                break;
            }
        }
        BOOST_LOG_TRIVIAL(info) << boost::format("old printer %1%, new process %2%, compatible %3%")%current_printer_name %new_process_name %process_compatible;
    }
    if (!process_compatible && !new_printer_name.empty() && !current_printer_name.empty() && (new_printer_name != current_printer_name)) {
        if (upward_compatible_printers.size() > 0) {
            for (int index = 0; index < upward_compatible_printers.size(); index++) {
                if (upward_compatible_printers[index] == new_printer_name) {
                    process_compatible = true;
                    machine_upwards = true;
                    break;
                }
            }
            if (!process_compatible) {
                boost::nowide::cout <<__FUNCTION__ << boost::format(": current 3mf file not support the new printer %1%")%new_printer_name;
                flush_and_exit(CLI_3MF_NEW_MACHINE_NOT_SUPPORTED);
            }
        }
        else {
            boost::nowide::cout <<__FUNCTION__ << boost::format(": current 3mf file not support upward_compatible_printers, can not change machine preset.");
            flush_and_exit(CLI_3MF_NOT_SUPPORT_MACHINE_CHANGE);
        }
    }

    if (!process_compatible) {
        boost::nowide::cout <<__FUNCTION__ << boost::format(": process not compatible with printer.");
        flush_and_exit(CLI_PROCESS_NOT_COMPATIBLE);
    }

    //create project embedded preset if needed
    Preset *new_preset = NULL;
    if (is_bbl_3mf && machine_upwards) {
        Preset *current_preset = NULL;
        size_t project_presets_count = project_presets.size();
        for (int index = 0; index < project_presets_count; index++)
        {
            if (project_presets[index]->name == current_process_name) {
                current_preset = project_presets[index];
                break;
            }
        }
        new_preset = new Preset(Preset::TYPE_PRINT, current_process_name);
        if (current_preset) {
            *new_preset = *current_preset;
            std::vector<std::string>& compatible_printers = new_preset->config.option<ConfigOptionStrings>("compatible_printers", true)->values;
            bool need_insert = true;
            for (int index = 0; index < compatible_printers.size(); index++) {
                if (compatible_printers[index] == new_printer_name) {
                    need_insert = false;
                    break;
                }
            }
            if (need_insert)
                compatible_printers.push_back(new_printer_name);
        }
        else {
            //store a project-embedded preset
            const std::vector<std::string>& process_keys = Preset::print_options();
            new_preset->config.apply_only(m_print_config, process_keys);
            std::vector<std::string>& compatible_printers = new_preset->config.option<ConfigOptionStrings>("compatible_printers", true)->values;
            compatible_printers = current_print_compatible_printers;
            compatible_printers.push_back(new_printer_name);
            if (current_process_system_name != current_process_name) {
                std::string& inherits = new_preset->config.option<ConfigOptionString>("inherits", true)->value;
                inherits = current_process_system_name;
            }
            new_preset->is_project_embedded = true;
            new_preset->type = Preset::TYPE_PRINT;
            //new_preset->name = current_process_name;
        }
        new_preset->name += "(auto)";
        new_preset->config.set("print_settings_id", new_preset->name, true);
        m_print_config.set("print_settings_id", new_preset->name, true);
        project_presets.push_back(new_preset);
    }

    //update seperate configs into full config
    auto update_full_config = [](DynamicPrintConfig& full_config, const DynamicPrintConfig& config, std::set<std::string>& diff_key_sets) {
        for (const t_config_option_key &opt_key : config.keys()) {
            if (!diff_key_sets.empty() && (diff_key_sets.find(opt_key) != diff_key_sets.end())) {
                //uptodate, diff keys, continue
                BOOST_LOG_TRIVIAL(info) << boost::format("keep key %1%")%opt_key;
                continue;
            }
            const ConfigOption *source_opt = config.option(opt_key);
            if (source_opt == nullptr) {
                // The key was not found in the source config, therefore it will not be initialized!
                boost::nowide::cerr << __FUNCTION__<<": can not found option " <<opt_key<<"from config." <<std::endl;
                return CLI_CONFIG_FILE_ERROR;
            }
            if (opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "model_id" || opt_key == "inherits" ||opt_key == "dev_model_name")
                continue;
            else {
                ConfigOption *dest_opt = full_config.option(opt_key, true);
                if (dest_opt == nullptr) {
                    boost::nowide::cerr << __FUNCTION__<<":can not create option " <<opt_key<<" to full_config "<<std::endl;
                    return CLI_CONFIG_FILE_ERROR;
                }
                dest_opt->set(source_opt);
                //*dest_opt = *source_opt;
            }
        }
        return 0;
    };

    std::vector<std::string>& different_settings = m_print_config.option<ConfigOptionStrings>("different_settings_to_system", true)->values;
    std::vector<std::string>& inherits_group = m_print_config.option<ConfigOptionStrings>("inherits_group", true)->values;
    inherits_group.resize(filament_count + 2, std::string());
    different_settings.resize(filament_count + 2, std::string());
    //set the machine settings into print config
    if (!new_printer_name.empty() || up_config_to_date) {
        std::vector<std::string> different_keys;

        if (new_printer_name.empty()) {
            std::string diff_settings;
            if (!different_settings.empty()) {
                diff_settings = different_settings[filament_count+1];
                Slic3r::unescape_strings_cstyle(diff_settings, different_keys);
            }
        }
        else {
            different_settings[filament_count+1] = "";
            inherits_group[filament_count+1] = "";
        }

        std::set<std::string> different_keys_set(different_keys.begin(), different_keys.end());
        BOOST_LOG_TRIVIAL(info) << boost::format("update printer config to newest, different size %1%")%different_keys_set.size();
        int ret = update_full_config(m_print_config, load_machine_config, different_keys_set);
        if (ret)
            flush_and_exit(ret);
    }

    //set the process settings into print config
    std::vector<std::string>& print_compatible_printers = m_print_config.option<ConfigOptionStrings>("print_compatible_printers", true)->values;
    if (!new_process_name.empty() || up_config_to_date) {
        std::vector<std::string> different_keys;

        if (new_process_name.empty()) {
            std::string diff_settings;
            if (!different_settings.empty()) {
                diff_settings = different_settings[0];
                Slic3r::unescape_strings_cstyle(diff_settings, different_keys);
                int remove_index = -1;
                for (int index = 0; index < different_keys.size(); index++) {
                    if (different_keys[index] == "compatible_printers") {
                        remove_index = index;
                        break;
                    }
                }
                if (remove_index != -1) {
                    different_keys[remove_index] = different_keys[different_keys.size() - 1];
                    different_keys.erase(different_keys.begin() + different_keys.size() - 1);
                    different_settings[0] = Slic3r::escape_strings_cstyle(different_keys);
                }
            }
            print_compatible_printers = std::move(current_print_compatible_printers);
        }
        else {
            different_settings[0] = "";
            inherits_group[0] = "";
            print_compatible_printers = std::move(new_print_compatible_printers);
        }

        std::set<std::string> different_keys_set(different_keys.begin(), different_keys.end());
        BOOST_LOG_TRIVIAL(info) << boost::format("update process config to newest, different size %1%")%different_keys_set.size();
        int ret = update_full_config(m_print_config, load_machine_config, different_keys_set);
        if (ret)
            flush_and_exit(ret);
    }

    if (machine_upwards) {
        print_compatible_printers.push_back(new_printer_name);

        std::string old_setting = different_settings[0];
        if (old_setting.empty())
            different_settings[0] = "compatible_printers";
        else {
            std::vector<std::string> different_keys;
            Slic3r::unescape_strings_cstyle(old_setting, different_keys);
            bool need_insert = true;
            for (int index = 0; index < different_keys.size(); index++) {
                if (different_keys[index] == "compatible_printers") {
                    need_insert = false;
                    break;
                }
            }
            if (need_insert)
                different_settings[0] = old_setting + ";compatible_printers";
        }
    }

    //set the filament settings into print config
    if ((load_filament_count > 0) || (up_config_to_date))
    {
        for (int index = 0; index < load_filaments_config.size(); index++) {
            DynamicPrintConfig&  config = load_filaments_config[index];
            int filament_index = load_filaments_index[index];
            std::vector<std::string> different_keys;

            if (load_filament_count > 0) {
                ConfigOptionStrings *opt_filament_settings = static_cast<ConfigOptionStrings *> (m_print_config.option("filament_settings_id", true));
                std::string& filament_name = load_filaments_name[index];
                ConfigOptionString* filament_name_setting = new ConfigOptionString(filament_name);
                if (opt_filament_settings->size() < filament_count)
                    opt_filament_settings->resize(filament_count, filament_name_setting);
                opt_filament_settings->set_at(filament_name_setting, filament_index-1, 0);
                config.erase("filament_settings_id");

                std::string& filament_id = load_filaments_id[index];
                ConfigOptionStrings *opt_filament_ids = static_cast<ConfigOptionStrings *> (m_print_config.option("filament_ids", true));
                ConfigOptionString* filament_id_setting = new ConfigOptionString(filament_id);
                if (opt_filament_ids->size() < filament_count)
                    opt_filament_ids->resize(filament_count, filament_id_setting);
                opt_filament_ids->set_at(filament_id_setting,  filament_index-1, 0);

                different_settings[filament_index] = "";
                inherits_group[filament_index] = "";
            }
            else {
                std::string diff_settings;

                if (!different_settings.empty()) {
                    diff_settings = different_settings[filament_index];
                    Slic3r::unescape_strings_cstyle(diff_settings, different_keys);
                }
            }

            //parse the filament value to index th
            //loop through options and apply them
            std::set<std::string> different_keys_set(different_keys.begin(), different_keys.end());
            BOOST_LOG_TRIVIAL(info) << boost::format("update filament %1%'s config to newest, different size %2%")%filament_index%different_keys_set.size();
            for (const t_config_option_key &opt_key : config.keys()) {
                if (!different_keys_set.empty() && (different_keys_set.find(opt_key) != different_keys_set.end())) {
                    //uptodate, diff keys, continue
                    BOOST_LOG_TRIVIAL(info) << boost::format("keep key %1%")%opt_key;
                    continue;
                }
                // Create a new option with default value for the key.
                // If the key is not in the parameter definition, or this ConfigBase is a static type and it does not support the parameter,
                // an exception is thrown if not ignore_nonexistent.

                const ConfigOption *source_opt = config.option(opt_key);
                if (source_opt == nullptr) {
                    // The key was not found in the source config, therefore it will not be initialized!
                    BOOST_LOG_TRIVIAL(error) << boost::format("can not find %1% from filament %2%: %3%")%opt_key%filament_index%load_filaments_name[index];
                    flush_and_exit(CLI_CONFIG_FILE_ERROR);
                }
                if (source_opt->is_scalar()) {
                    if (opt_key == "compatible_printers_condition") {
                        ConfigOption *opt = m_print_config.option("compatible_machine_expression_group", true);
                        ConfigOptionStrings* opt_vec_dst = static_cast<ConfigOptionStrings*>(opt);
                        if (opt_vec_dst->size() == 0)
                            opt_vec_dst->resize(filament_count+2, new ConfigOptionString());
                        opt_vec_dst->set_at(source_opt, filament_index, 0);
                    }
                    else if (opt_key == "compatible_prints_condition") {
                        ConfigOption *opt = m_print_config.option("compatible_process_expression_group", true);
                        ConfigOptionStrings* opt_vec_dst = static_cast<ConfigOptionStrings*>(opt);
                        if (opt_vec_dst->size() == 0)
                            opt_vec_dst->resize(filament_count, new ConfigOptionString());
                        opt_vec_dst->set_at(source_opt, filament_index-1, 0);
                    }
                    else {
                        //skip the scalar values
                        BOOST_LOG_TRIVIAL(info) << boost::format("skip scalar option %1% from filament %2%: %3%")%opt_key%filament_index%load_filaments_name[index];;
                        continue;
                    }
                }
                else
                {
                    if (opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "model_id" || opt_key == "dev_model_name" || opt_key == "filament_settings_id")
                        continue;
                    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_LOG_TRIVIAL(error) << boost::format("can not create option %1% to config, from filament %2%: %3%")%opt_key%filament_index%load_filaments_name[index];
                        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);
                    opt_vec_dst->set_at(opt_vec_src, filament_index-1, 0);
                }
            }
        }
    }

    //BBS: set default to ptFFF
    if (printer_technology == ptUnknown)
        printer_technology = ptFFF;

    //BBS: merge these models into one
    BOOST_LOG_TRIVIAL(info) << "total " << m_models.size() << " models, "<<orients_requirement.size()<<" objects"<<std::endl;
    if (m_models.size() > 1)
    {
        BOOST_LOG_TRIVIAL(info) << "merge all the models into one\n";
        Model m;
        m.set_backup_path(m_models[0].get_backup_path());
        for (auto& model : m_models)
            for (ModelObject* o : model.objects)
            {
                ModelObject* new_object = m.add_object(*o);
                //BOOST_LOG_TRIVIAL(info) << "object "<<o->name <<", id :" << o->id().id << "\n";
                orients_requirement.emplace(new_object->id().id, orients_requirement[o->id().id]);
                orients_requirement.erase(o->id().id);
            }
        m.add_default_instances();
        m_models.clear();
        m_models.emplace_back(std::move(m));
    }

    // Apply command line options to a more specific DynamicPrintConfig which provides normalize()
    // (command line options override --load files)
    m_print_config.apply(m_extra_config, true);
    // Normalizing after importing the 3MFs / AMFs
    m_print_config.normalize_fdm();

    m_print_config.option<ConfigOptionEnum<PrinterTechnology>>("printer_technology", true)->value = printer_technology;

    // Initialize full print configs for both the FFF and SLA technologies.
    FullPrintConfig    fff_print_config;
    //SLAFullPrintConfig sla_print_config;

    // Synchronize the default parameters and the ones received on the command line.
    if (printer_technology == ptFFF) {
        fff_print_config.apply(m_print_config, true);
        m_print_config.apply(fff_print_config, true);
    } else {
        boost::nowide::cerr << "invalid printer_technology " << std::endl;
        flush_and_exit(CLI_INVALID_PRINTER_TECH);
        /*assert(printer_technology == ptSLA);
        sla_print_config.filename_format.value = "[input_filename_base].sl1";

        // The default bed shape should reflect the default display parameters
        // and not the fff defaults.
        double w = sla_print_config.display_width.getFloat();
        double h = sla_print_config.display_height.getFloat();
        sla_print_config.printable_area.values = { Vec2d(0, 0), Vec2d(w, 0), Vec2d(w, h), Vec2d(0, h) };

        sla_print_config.apply(m_print_config, true);
        m_print_config.apply(sla_print_config, true);*/
    }

    std::map<std::string, std::string> validity = m_print_config.validate(true);
    if (!validity.empty()) {
        boost::nowide::cerr << "Param values in 3mf/config error: "<< std::endl;
        for (std::map<std::string, std::string>::iterator it=validity.begin(); it!=validity.end(); ++it)
            boost::nowide::cerr << it->first <<": "<< it->second << std::endl;
        flush_and_exit(CLI_INVALID_VALUES_IN_3MF);
    }

    //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;
    std::string bed_texture;
    if (m_models.size() > 0)
    {
        if (translate_old) {
            current_width = bedfs[2].x() - bedfs[0].x();
            current_depth = bedfs[2].y() - bedfs[0].y();
            current_height = print_height;
            BOOST_LOG_TRIVIAL(info) << boost::format("translate old 3mf, switch to old bed size,{%1%, %2%, %3%}")%(current_width + bed3d_ax3s_default_tip_radius)%(current_depth+bed3d_ax3s_default_tip_radius) %current_height;
            partplate_list.reset_size(current_width + bed3d_ax3s_default_tip_radius, current_depth + bed3d_ax3s_default_tip_radius, current_height, false);
        }
        else {
            partplate_list.reset_size(bedfs[2].x() - bedfs[0].x(), bedfs[2].y() - bedfs[0].y(), print_height, false);
        }
        partplate_list.set_shapes(bedfs, excluse_areas, bed_texture, height_to_lid, height_to_rod);
        plate_stride = partplate_list.plate_stride_x();
        BOOST_LOG_TRIVIAL(info) << "bed size, x="<<bedfs[2].x() - bedfs[0].x()<<",y="<<bedfs[2].y() - bedfs[0].y()<<",z="<< print_height <<"\n";
    }
    if (plate_data_src.size() > 0)
    {
        partplate_list.load_from_3mf_structure(plate_data_src);
        //BBS: translate old 3mf to correct positions
        if (translate_old) {
            //translate the objects
            int plate_count = partplate_list.get_plate_count();
            for (int index = 1; index < plate_count; index ++) {
                Slic3r::GUI::PartPlate* cur_plate = (Slic3r::GUI::PartPlate *)partplate_list.get_plate(index);

                Vec3d cur_origin = cur_plate->get_origin();
                Vec3d new_origin = partplate_list.compute_origin_using_new_size(index, current_width, current_depth);

                cur_plate->translate_all_instance(new_origin - cur_origin);
            }
            BOOST_LOG_TRIVIAL(info) << boost::format("translate old 3mf, switch back to current bed size,{%1%, %2%, %3%}")%current_width %current_depth %current_height;
            partplate_list.reset_size(current_width, current_depth, current_height, true, true);
        }
    }

    /*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";
#if defined(__linux__) || defined(__LINUX__)
    if (g_cli_callback_mgr.is_started()) {
        PrintBase::SlicingStatus slicing_status{1, "Loading files finished"};
        cli_status_callback(slicing_status);
    }
#endif

    for (auto const &opt_key : m_transforms) {
        BOOST_LOG_TRIVIAL(info) << "process transform " << opt_key << "\n";
        if (opt_key == "merge") {
            //BBS: always merge, do nothing here
            /*Model m;
            for (auto& model : m_models)
                for (ModelObject* o : model.objects)
                    m.add_object(*o);
            // Rearrange instances unless --dont-arrange is supplied
            if (!m_config.opt_bool("dont_arrange")) {
                m.add_default_instances();
                if (this->has_print_action())
                    arrange_objects(m, bed, arrange_cfg);
                else
                    arrange_objects(m, InfiniteBed{}, arrange_cfg);
            }
            m_models.clear();
            m_models.emplace_back(std::move(m));*/
        }
        else if (opt_key == "convert_unit") {
            for (auto& model : m_models) {
                if (model.looks_like_saved_in_meters()) {
                    BOOST_LOG_TRIVIAL(info) << "convert from meter to millimeter\n";
                    model.convert_from_meters(true);
                }
                else if (model.looks_like_imperial_units()) {
                    BOOST_LOG_TRIVIAL(info) << "convert from inch to millimeter\n";
                    model.convert_from_imperial_units(true);
                }
            }
        }
        else if (opt_key == "orient") {
            for (auto& model : m_models)
                for (ModelObject* o : model.objects)
                {
                    // coconut: always orient instance instead of object
                    for (ModelInstance* mi : o->instances)
                    {
                        orientation::orient(mi);
                    }
                    BOOST_LOG_TRIVIAL(info) << "orient object, name=" << o->name <<",id="<<o->id().id<<std::endl;
                    //BBS: clear the orient objects lists
                    orients_requirement[o->id().id] = false;
                }
        }
        else if (opt_key == "copy") {
            for (auto &model : m_models) {
                const bool all_objects_have_instances = std::none_of(
                    model.objects.begin(), model.objects.end(),
                    [](ModelObject* o){ return o->instances.empty(); }
                );

                int dups = m_config.opt_int("copy");
                if (!all_objects_have_instances) model.add_default_instances();

                try {
                    if (dups > 1) {
                        // if all input objects have defined position(s) apply duplication to the whole model
                        duplicate(model, size_t(dups), beds, arrange_cfg);
                    } else {
                        arrange_objects(model, beds, arrange_cfg);
                    }
                } catch (std::exception &ex) {
                    boost::nowide::cerr << "error: " << ex.what() << std::endl;
                    flush_and_exit(CLI_COPY_OBJECTS_ERROR);
                }
            }
        } else if (opt_key == "center") {
        	user_center_specified = true;
            for (auto &model : m_models) {
                model.add_default_instances();
                // this affects instances:
                model.center_instances_around_point(m_config.option<ConfigOptionPoint>("center")->value);
                // this affects volumes:
                //FIXME Vojtech: Who knows why the complete model should be aligned with Z as a single rigid body?
                //model.align_to_ground();
                BoundingBoxf3 bbox;
                for (ModelObject *model_object : model.objects)
                    // We are interested into the Z span only, therefore it is sufficient to measure the bounding box of the 1st instance only.
                    bbox.merge(model_object->instance_bounding_box(0, false));
                for (ModelObject *model_object : model.objects)
                    for (ModelInstance *model_instance : model_object->instances)
                        model_instance->set_offset(Z, model_instance->get_offset(Z) - bbox.min.z());
            }
        } else if (opt_key == "align_xy") {
            const Vec2d &p = m_config.option<ConfigOptionPoint>("align_xy")->value;
            for (auto &model : m_models) {
                BoundingBoxf3 bb = model.bounding_box();
                // this affects volumes:
                model.translate(-(bb.min.x() - p.x()), -(bb.min.y() - p.y()), -bb.min.z());
            }
        } else if (opt_key == "arrange") {
            //BBS: arrange 0 means disable, 1 means force arrange, others means auto
            int arrange_option = m_config.option<ConfigOptionInt>("arrange")->value;

            if (arrange_option == 0)
            {
                need_arrange = false;
            }
            else if (arrange_option == 1)
            {
                need_arrange = true;
            }
            else
            {
                //auto arrange, keep the original logic
            }
        } else if (opt_key == "ensure_on_bed") {
            // do nothing, the value is used later
        } else if (opt_key == "rotate") {
            for (auto &model : m_models)
                for (auto &o : model.objects)
                    // this affects volumes:
                    o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), Z);
        } else if (opt_key == "rotate_x") {
            for (auto &model : m_models)
                for (auto &o : model.objects)
                    // this affects volumes:
                    o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), X);
        } else if (opt_key == "rotate_y") {
            for (auto &model : m_models)
                for (auto &o : model.objects)
                    // this affects volumes:
                    o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), Y);
        } else if (opt_key == "scale") {
            for (auto &model : m_models)
                for (auto &o : model.objects)
                    // this affects volumes:
                    o->scale(m_config.get_abs_value(opt_key, 1));
        } else if (opt_key == "scale_to_fit") {
            const Vec3d &opt = m_config.opt<ConfigOptionPoint3>(opt_key)->value;
            if (opt.x() <= 0 || opt.y() <= 0 || opt.z() <= 0) {
                boost::nowide::cerr << "--scale-to-fit requires a positive volume" << std::endl;
                flush_and_exit(CLI_SCALE_TO_FIT_ERROR);
            }
            for (auto &model : m_models)
                for (auto &o : model.objects)
                    // this affects volumes:
                    o->scale_to_fit(opt);
        } else if (opt_key == "cut" || opt_key == "cut_x" || opt_key == "cut_y") {
            std::vector<Model> new_models;
            for (auto &model : m_models) {
                model.translate(0, 0, -model.bounding_box().min.z());  // align to z = 0
                size_t num_objects = model.objects.size();
                for (size_t i = 0; i < num_objects; ++ i) {

#if 0
                    if (opt_key == "cut_x") {
                        o->cut(X, m_config.opt_float("cut_x"), &out);
                    } else if (opt_key == "cut_y") {
                        o->cut(Y, m_config.opt_float("cut_y"), &out);
                    } else if (opt_key == "cut") {
                        o->cut(Z, m_config.opt_float("cut"), &out);
                    }
#else
                    ModelObject* object = model.objects.front();
                    const BoundingBoxf3& box = object->bounding_box();
                    const float Margin = 20.0;
                    const float max_x = box.size()(0) / 2.0 + Margin;
                    const float min_x = -max_x;
                    const float max_y = box.size()(1) / 2.0 + Margin;
                    const float min_y = -max_y;

                    std::array<Vec3d, 4> plane_points;
                    plane_points[0] = { min_x, min_y, 0 };
                    plane_points[1] = { max_x, min_y, 0 };
                    plane_points[2] = { max_x, max_y, 0 };
                    plane_points[3] = { min_x, max_y, 0 };
                    for (Vec3d& point : plane_points) {
                        point += box.center();
                    }
                    model.objects.front()->cut(0, plane_points, ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower);
#endif
                    model.delete_object(size_t(0));
                }
            }

            // TODO: copy less stuff around using pointers
            m_models = new_models;

            if (m_actions.empty())
                m_actions.push_back("export_stl");
        }
#if 0
        else if (opt_key == "cut_grid") {
            std::vector<Model> new_models;
            for (auto &model : m_models) {
                TriangleMesh mesh = model.mesh();
                mesh.repair();

                std::vector<TriangleMesh> meshes = mesh.cut_by_grid(m_config.option<ConfigOptionPoint>("cut_grid")->value);
                size_t i = 0;
                for (TriangleMesh* m : meshes) {
                    Model out;
                    auto o = out.add_object();
                    o->add_volume(*m);
                    o->input_file += "_" + std::to_string(i++);
                    delete m;
                }
            }

            // TODO: copy less stuff around using pointers
            m_models = new_models;

            if (m_actions.empty())
                m_actions.push_back("export_stl");
        }
#endif
        else if (opt_key == "split") {
            for (Model &model : m_models) {
                size_t num_objects = model.objects.size();
                for (size_t i = 0; i < num_objects; ++ i) {
                    ModelObjectPtrs new_objects;
                    model.objects.front()->split(&new_objects);
                    model.delete_object(size_t(0));
                }
            }
        } else if (opt_key == "repair") {
            // Models are repaired by default.
            //for (auto &model : m_models)
            //    model.repair();
        } else {
            boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl;
            flush_and_exit(CLI_UNSUPPORTED_OPERATION);
        }
    }

    BOOST_LOG_TRIVIAL(info) << "finished model pre-process commands\n";
    bool oriented_or_arranged = false;
    //BBS: add orient and arrange logic here
    for (auto& model : m_models)
    {
        for (ModelObject* o : model.objects)
        {
            if (orients_requirement[o->id().id])
            {
                BOOST_LOG_TRIVIAL(info) << "Before process command, Orient object, name=" << o->name <<",id="<<o->id().id<<std::endl;
                orientation::orient(o);
                oriented_or_arranged = true;
            }
            else
            {
                BOOST_LOG_TRIVIAL(info) << "Before process command, no need to orient, object id :" << o->id().id<<std::endl;
            }
        }
    }
    //BBS: clear the orient objects lists
    orients_requirement.clear();
    oriented_or_arranged |= need_arrange;

    if (need_arrange)
    {
        ArrangePolygons selected, unselected, unprintable, locked_aps;
        BOOST_LOG_TRIVIAL(info) << "Will arrange now, need_arrange="<<need_arrange<<"\n";

        for (Model &model : m_models)
        {
            //Step-1: prepare arrange polygons
            for (size_t oidx = 0; oidx < model.objects.size(); ++oidx)
            {
                ModelObject* mo = model.objects[oidx];
                for (size_t inst_idx = 0; inst_idx < mo->instances.size(); ++inst_idx)
                {
                    ModelInstance* minst = mo->instances[inst_idx];
                    ArrangePolygon ap = get_instance_arrange_poly(minst, m_print_config);

                    //preprocess by partplate list
                    //remove the locked plate's instances, neither in selected, nor in un-selected
                    bool locked = partplate_list.preprocess_arrange_polygon(oidx, inst_idx, ap, true);
                    if (!locked)
                    {
                        ap.itemid = selected.size();
                        if (minst->printable)
                            selected.emplace_back(ap);
                        else
                            unprintable.emplace_back(ap);
                    }
                    else
                    {
                        //skip this object due to be locked in plate
                        ap.itemid = locked_aps.size();
                        locked_aps.emplace_back(ap);
                        boost::nowide::cout <<__FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, instance_id %2%") % oidx % inst_idx;
                    }
                }
            }

            //add the virtual object into unselect list if has
            partplate_list.preprocess_exclude_areas(unselected);

            //Step-2:prepare the arrange params
            arrange_cfg.allow_rotations  = true;
            arrange_cfg.min_obj_distance = scaled(6.0);
            //BBS: add specific params
            arrange_cfg.is_seq_print = false;
            arrange_cfg.bed_shrink_x = 0;
            arrange_cfg.bed_shrink_y = 0;
            double skirt_distance = m_print_config.opt_float("skirt_distance");
            double brim_width = m_print_config.opt_float("brim_width");
            arrange_cfg.brim_skirt_distance = skirt_distance + brim_width;
            BOOST_LOG_TRIVIAL(info) << boost::format("Arrange Params: brim_skirt_distance=%1%, min_obj_distance=%2%, is_seq_print=%3%\n") %  arrange_cfg.brim_skirt_distance % arrange_cfg.min_obj_distance % arrange_cfg.is_seq_print;

            // Note: skirt_distance is now defined between outermost brim and skirt, not the object and skirt.
            // So we can't do max but do adding instead.
            arrange_cfg.bed_shrink_x += arrange_cfg.brim_skirt_distance;
            arrange_cfg.bed_shrink_y += arrange_cfg.brim_skirt_distance;
            // shrink bed
            beds[0] += Point(scaled(arrange_cfg.bed_shrink_x), scaled(arrange_cfg.bed_shrink_y));
            beds[1] += Point(-scaled(arrange_cfg.bed_shrink_x), scaled(arrange_cfg.bed_shrink_y));
            beds[2] += Point(-scaled(arrange_cfg.bed_shrink_x), -scaled(arrange_cfg.bed_shrink_y));
            beds[3] += Point(scaled(arrange_cfg.bed_shrink_x), -scaled(arrange_cfg.bed_shrink_y));

            // do not inflate brim_width. Objects are allowed to have overlapped brim.
            std::for_each(selected.begin(), selected.end(), [&](auto& ap) {ap.inflation = arrange_cfg.min_obj_distance / 2; });

            {
                BOOST_LOG_TRIVIAL(info) << "items selected before arranging: ";
                for (auto selected : selected)
                    BOOST_LOG_TRIVIAL(info) << selected.name << ", extruder: " << selected.extrude_ids.back() << ", bed: " << selected.bed_idx
                                            << ", trans: " << selected.translation.transpose();
            }
            arrange_cfg.progressind= [](unsigned st, std::string str = "") {
                boost::nowide::cout << "st=" << st << ", " << str << std::endl;
            };

            //Step-3:do the arrange
            arrangement::arrange(selected, unselected, beds, arrange_cfg);
            arrangement::arrange(unprintable, {}, beds, arrange_cfg);

            //Step-4:postprocess by partplate list&&apply the result
            int bed_idx_max = 0;
            //clear all the relations before apply the arrangement results
            partplate_list.clear();

            // Apply the arrange result to all selected objects
            for (ArrangePolygon &ap : selected) {
                //BBS: partplate postprocess
                partplate_list.postprocess_bed_index_for_selected(ap);

                bed_idx_max = std::max(ap.bed_idx, bed_idx_max);
                boost::nowide::cout<< "after arrange: name=" << ap.name << boost::format(",bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale<double>(ap.translation(X)) % unscale<double>(ap.translation(Y)) << "\n";
            }
            for (ArrangePolygon &ap : locked_aps) {
                bed_idx_max = std::max(ap.bed_idx, bed_idx_max);

                partplate_list.postprocess_arrange_polygon(ap, false);

                ap.apply();
            }

            // Apply the arrange result to all selected objects
            for (ArrangePolygon &ap : selected) {
                //BBS: partplate postprocess
                partplate_list.postprocess_arrange_polygon(ap, true);

                ap.apply();
            }

            // Move the unprintable items to the last virtual bed.
            for (ArrangePolygon &ap : unprintable) {
                ap.bed_idx += bed_idx_max + 1;
                partplate_list.postprocess_arrange_polygon(ap, true);

                ap.apply();
            }

            //BBS: reload all objects due to arrange
            partplate_list.rebuild_plates_after_arrangement();
        }
    }

    // All transforms have been dealt with. Now ensure that the objects are on bed.
    // (Unless the user said otherwise.)
    //BBS: current only support models on bed, 0407 sinking supported
    //if (m_config.opt_bool("ensure_on_bed"))
    //    for (auto &model : m_models)
    //        for (auto &o : model.objects)
    //            o->ensure_on_bed();

    // loop through action options
    bool export_to_3mf = false, load_slicedata = false, export_slicedata = false, export_slicedata_error = false;
    bool no_check = false;
    std::string export_3mf_file, load_slice_data_dir, export_slice_data_dir;
    std::string outfile_dir = m_config.opt_string("outputdir");
    std::vector<ThumbnailData*> calibration_thumbnails;
    int max_slicing_time_per_plate = 0, max_triangle_count_per_plate = 0;
    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") {
            //already processed before
        } else if (opt_key == "load_slicedata") {
            load_slicedata = true;
            load_slice_data_dir = m_config.opt_string(opt_key);
            if (export_slicedata) {
                BOOST_LOG_TRIVIAL(error) << "should not set load_slicedata and export_slicedata together." << std::endl;
                flush_and_exit(CLI_INVALID_PARAMS);
            }
        } else if (opt_key == "export_settings") {
            //FIXME check for mixing the FFF / SLA parameters.
            // or better save fff_print_config vs. sla_print_config
            //m_print_config.save(m_config.opt_string("save"));
            m_print_config.save_to_json(m_config.opt_string(opt_key), std::string("project_settings"), std::string("project"), std::string(SoftFever_VERSION));
        } else if (opt_key == "info") {
            // --info works on unrepaired model
            for (Model &model : m_models) {
                model.add_default_instances();
                model.print_info();
            }
        } else if (opt_key == "uptodate") {
            //already processed before
        } else if (opt_key == "mtcpp") {
            max_triangle_count_per_plate = m_config.option<ConfigOptionInt>("mtcpp")->value;
        } else if (opt_key == "mstpp") {
            max_slicing_time_per_plate = m_config.option<ConfigOptionInt>("mstpp")->value;
        } else if (opt_key == "export_stl") {
            for (auto &model : m_models)
                model.add_default_instances();
            if (! this->export_models(IO::STL))
                flush_and_exit(CLI_EXPORT_STL_ERROR);
        } else if (opt_key == "export_obj") {
            for (auto &model : m_models)
                model.add_default_instances();
            if (! this->export_models(IO::OBJ))
                flush_and_exit(CLI_EXPORT_OBJ_ERROR);
        }/* else if (opt_key == "export_amf") {
            if (! this->export_models(IO::AMF))
                return 1;
        } */else if (opt_key == "export_3mf") {
            export_to_3mf = true;
            export_3mf_file = m_config.opt_string(opt_key);
        }else if(opt_key=="no_check"){
            no_check = m_config.opt_bool(opt_key);
        //} else if (opt_key == "export_gcode" || opt_key == "export_sla" || opt_key == "slice") {
        } else if (opt_key == "normative_check") {
            //already processed before
        } else if (opt_key == "export_slicedata") {
            export_slicedata = true;
            export_slice_data_dir = m_config.opt_string(opt_key);
            if (load_slicedata) {
                BOOST_LOG_TRIVIAL(error) << "should not set load_slicedata and export_slicedata together." << std::endl;
                flush_and_exit(CLI_INVALID_PARAMS);
            }
        } else if (opt_key == "slice") {
            //BBS: slice 0 means all plates, i means plate i;
            plate_to_slice = m_config.option<ConfigOptionInt>("slice")->value;
            bool pre_check = (plate_to_slice == 0)?true:false;
            if (partplate_list.get_plate_count() == 1)
                pre_check = false;
            bool finished = false;

            //skip model object
            std::map<int, bool> skip_maps;
            if (need_skip) {
                BOOST_LOG_TRIVIAL(info) << boost::format("need to skip objects, size %1%:")%skip_objects.size();
                for (int index = 0; index < skip_objects.size(); index++)
                {
                    skip_maps[skip_objects[index]] = false;
                    BOOST_LOG_TRIVIAL(info) << boost::format("object %1%, id %2%")%index %skip_objects[index];
                }
            }
            /*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;
#if defined(__linux__) || defined(__LINUX__)
            if (g_cli_callback_mgr.is_started()) {
                PrintBase::SlicingStatus slicing_status{3, "Prepare slicing"};
                cli_status_callback(slicing_status);
            }
#endif
            // 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;

                while(!finished)
                {
                    //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;
                        }
                        BOOST_LOG_TRIVIAL(info) << boost::format("Plate %1%: pre_check %2%, start")%(index+1)%pre_check;
                        long long start_time = 0, end_time = 0;
                        start_time = (long long)Slic3r::Utils::get_current_time_utc();
                        //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 if ((plate_to_slice != 0) || pre_check) {
                            long long triangle_count = 0;
                            int printable_instances = 0;
                            int skipped_count = 0;
                            for (ModelObject* model_object : model.objects)
                                for (ModelInstance *i : model_object->instances)
                                {
                                    if (skip_maps.find(i->loaded_id) != skip_maps.end()) {
                                        skip_maps[i->loaded_id] = true;
                                        i->printable = false;
                                        if (i->print_volume_state == ModelInstancePVS_Inside) {
                                            skipped_count++;
                                            BOOST_LOG_TRIVIAL(info) << boost::format("Plate %1%: skip object %2%.")%(index+1)%i->loaded_id;
                                            //need to regenerate the thumbnail
                                            if (plate_data_src.size() > index) {
                                                if (!plate_data_src[index]->thumbnail_file.empty()) {
                                                    BOOST_LOG_TRIVIAL(info) << boost::format("Plate %1%: clear loaded thumbnail %2%.")%(index+1)%plate_data_src[index]->thumbnail_file;
                                                    plate_data_src[index]->thumbnail_file.clear();
                                                }
                                                if (!plate_data_src[index]->top_file.empty()) {
                                                    BOOST_LOG_TRIVIAL(info) << boost::format("Plate %1%: clear loaded top_thumbnail %2%.")%(index+1)%plate_data_src[index]->top_file;
                                                    plate_data_src[index]->top_file.clear();
                                                }
                                                if (!plate_data_src[index]->pick_file.empty()) {
                                                    BOOST_LOG_TRIVIAL(info) << boost::format("Plate %1%: clear loaded pick_thumbnail %2%.")%(index+1)%plate_data_src[index]->pick_file;
                                                    plate_data_src[index]->pick_file.clear();
                                                }
                                            }
                                        }
                                        continue;
                                    }

                                    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);
                                    }
                                    else if ((max_triangle_count_per_plate != 0) && (i->print_volume_state == ModelInstancePVS_Inside))
                                    {
                                        for (const ModelVolume* vol : model_object->volumes)
                                        {
                                            if (vol->is_model_part()) {
                                                size_t volume_triangle_count = vol->mesh().facets_count();
                                                triangle_count += volume_triangle_count;
                                                BOOST_LOG_TRIVIAL(info) << boost::format("volume triangle count %1%, total %2%")%volume_triangle_count %triangle_count;
                                                if (triangle_count > max_triangle_count_per_plate)
                                                {
                                                    BOOST_LOG_TRIVIAL(error) << "plate "<< index+1<< ": triangle count " << triangle_count <<" exceeds the limit:" << max_triangle_count_per_plate;
                                                    flush_and_exit(CLI_TRIANGLE_COUNT_EXCEEDS_LIMIT);
                                                }
                                            }
                                        }
                                    }

                                    if (i->print_volume_state == ModelInstancePVS_Inside)
                                        printable_instances++;
                                }

                            if (printable_instances == 0) {
                                BOOST_LOG_TRIVIAL(error) << "plate "<< index+1<< ": Nothing to be sliced, after skipping "<<skipped_count<<" objects."<< std::endl;
                                flush_and_exit(CLI_NO_SUITABLE_OBJECTS_AFTER_SKIP);
                            }
                        }
                        // 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
                        DynamicPrintConfig new_print_config = m_print_config;
                        new_print_config.apply(*part_plate->config());
                        new_print_config.apply(m_extra_config, true);
                        print->apply(model, new_print_config);
                        BOOST_LOG_TRIVIAL(info) << boost::format("set no_check to %1%:")%no_check;
                        print->set_no_check_flag(no_check);//BBS
                        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 {
                            if (pre_check) //continue to next plate directly
                                continue;
                            try {
                                std::string outfile_final;
                                BOOST_LOG_TRIVIAL(info) << "start Print::process for partplate "<<index+1 << std::endl;
#if defined(__linux__) || defined(__LINUX__)
                                BOOST_LOG_TRIVIAL(info) << "cli callback mgr started:  "<<g_cli_callback_mgr.m_started << std::endl;
                                if (g_cli_callback_mgr.is_started()) {
                                    BOOST_LOG_TRIVIAL(info) << "set print's callback to cli_status_callback.";
                                    print->set_status_callback(cli_status_callback);
                                    g_cli_callback_mgr.set_plate_info(index+1, (plate_to_slice== 0)?partplate_list.get_plate_count():1);
                                    if (!warning.string.empty()) {
                                        PrintBase::SlicingStatus slicing_status{4, warning.string, 0, 0};
                                        cli_status_callback(slicing_status);
                                    }
                                    else {
                                        PrintBase::SlicingStatus slicing_status{4, "Slicing begins"};
                                        cli_status_callback(slicing_status);
                                    }
                                }
#endif
                                if (load_slicedata) {
                                    std::string plate_dir = load_slice_data_dir+"/"+std::to_string(index+1);
                                    int ret = print->load_cached_data(plate_dir);
                                    if (ret) {
                                        BOOST_LOG_TRIVIAL(warning) << "plate "<< index+1<< ": load Slicing data error, ret=" << ret;
                                        BOOST_LOG_TRIVIAL(warning) << "plate "<< index+1<< ": switch normal slicing";
                                        print->process();
                                    }
                                    else {
                                        BOOST_LOG_TRIVIAL(info) << "plate "<< index+1<< ": load cached data success, go on.";
#if defined(__linux__) || defined(__LINUX__)
                                        if (g_cli_callback_mgr.is_started()) {
                                            PrintBase::SlicingStatus slicing_status{69, "Cache data loaded"};
                                            cli_status_callback(slicing_status);
                                        }
#endif
                                        print->process(true);
                                        BOOST_LOG_TRIVIAL(info) << "plate "<< index+1<< ": finished print::process.";
                                    }
                                }
                                else {
                                    print->process();
                                }
                                if (printer_technology == ptFFF) {
                                    std::string conflict_result = dynamic_cast<Print *>(print)->get_conflict_string();
                                    if (!conflict_result.empty()) {
                                       BOOST_LOG_TRIVIAL(error) << "plate "<< index+1<< ": found slicing result conflict!"<< std::endl;
                                       flush_and_exit(CLI_GCODE_PATH_CONFLICTS);
                                    }
                                    // 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.
                                //run_post_process_scripts(outfile, print->full_print_config());
                                BOOST_LOG_TRIVIAL(info) << "Slicing result exported to " << outfile << std::endl;
                                part_plate->update_slice_result_valid_state(true);
#if defined(__linux__) || defined(__LINUX__)
                                if (g_cli_callback_mgr.is_started()) {
                                    PrintBase::SlicingStatus slicing_status{100, "Slicing finished"};
                                    cli_status_callback(slicing_status);
                                }
#endif
                                if (export_slicedata) {
                                    BOOST_LOG_TRIVIAL(info) << "plate "<< index+1<< ":will export Slicing data to " << export_slice_data_dir;
                                    std::string plate_dir = export_slice_data_dir+"/"+std::to_string(index+1);
                                    bool with_space = (get_logging_level() >= 4)?true:false;
                                    int ret = print->export_cached_data(plate_dir, with_space);
                                    if (ret) {
                                        BOOST_LOG_TRIVIAL(error) << "plate "<< index+1<< ": export Slicing data error, ret=" << ret;
                                        export_slicedata_error = true;
                                        if (fs::exists(plate_dir))
                                            fs::remove_all(plate_dir);
                                    }
                                }
                                if (max_slicing_time_per_plate != 0) {
                                    end_time = (long long)Slic3r::Utils::get_current_time_utc();
                                    long long time_cost = end_time - start_time;
                                    if (time_cost > max_slicing_time_per_plate) {
                                        BOOST_LOG_TRIVIAL(error) << boost::format("plate %1%'s slice time %2% exceeds the limit %3%, return error.")
                                            %(index+1) %time_cost %max_slicing_time_per_plate;
                                        flush_and_exit(CLI_SLICING_TIME_EXCEEDS_LIMIT);
                                    }
                                }
                            } catch (const std::exception &ex) {
                                BOOST_LOG_TRIVIAL(error) << "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);
                            }
                        }
                    }
                    if (pre_check)
                        pre_check = false;
                    else
                        finished = true;
                }//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(0, 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
        std::vector<ThumbnailData *> thumbnails, top_thumbnails, pick_thumbnails;
        std::vector<PlateBBoxData*> plate_bboxes;
        PlateDataPtrs plate_data_list;
        partplate_list.store_to_3mf_structure(plate_data_list);

        if (!outfile_dir.empty()) {
            export_3mf_file = outfile_dir + "/"+export_3mf_file;
        }

#if defined(__linux__) || defined(__LINUX__)
        if (g_cli_callback_mgr.is_started()) {
            PrintBase::SlicingStatus slicing_status{94, "Generate thumbnails"};
            cli_status_callback(slicing_status);
        }
#endif

        bool need_regenerate_thumbnail = oriented_or_arranged || regenerate_thumbnails;
        bool need_regenerate_top_thumbnail = oriented_or_arranged || regenerate_thumbnails;
        bool need_create_thumbnail_group = false,  need_create_top_group = false;

        // 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];
            bool skip_this_plate = ((plate_to_slice != 0) && (plate_to_slice != (i + 1)))?true:false;

            for (auto it = plate_data->slice_filaments_info.begin(); it != plate_data->slice_filaments_info.end(); it++) {
                //it->filament_id = filament_id?filament_id->get_at(it->id):"unknown";
                std::string display_filament_type;
                it->type  = m_print_config.get_filament_type(display_filament_type, it->id);
                it->color = filament_color ? filament_color->get_at(it->id) : "#FFFFFF";
            }

            if (!plate_data->plate_thumbnail.is_valid()) {
                if (!oriented_or_arranged && !regenerate_thumbnails && plate_data_src.size() > i)
                    plate_data->thumbnail_file = plate_data_src[i]->thumbnail_file;
                BOOST_LOG_TRIVIAL(info) << boost::format("thumbnails stage: plate %1%'s thumbnail data is invalid, check the file %2% exist or not")%(i+1) %plate_data->thumbnail_file;
                if (plate_data->thumbnail_file.empty() || (!boost::filesystem::exists(plate_data->thumbnail_file))) {
                    BOOST_LOG_TRIVIAL(info) << boost::format("thumbnails stage: plate %1%'s thumbnail file also not there, need to regenerate")%(i+1);
                    if (!skip_this_plate) {
                        need_regenerate_thumbnail = true;
                        need_create_thumbnail_group = true;
                    }
                }
                else {
                    BOOST_LOG_TRIVIAL(info) << boost::format("thumbnails stage: plate %1%'s thumbnail file exists, no need to regenerate")%(i+1);
                }
            }
            else {
                if (regenerate_thumbnails)
                    plate_data->plate_thumbnail.reset();

                if (!skip_this_plate) {
                    need_create_thumbnail_group = true;
                }
            }

            if (plate_data->top_file.empty() || plate_data->pick_file.empty()) {
                if (!regenerate_thumbnails && (plate_data_src.size() > i)) {
                    plate_data->top_file = plate_data_src[i]->top_file;
                    plate_data->pick_file = plate_data_src[i]->pick_file;
                }
                if (plate_data->top_file.empty()|| plate_data->pick_file.empty()
                    || (!boost::filesystem::exists(plate_data->top_file)) || (!boost::filesystem::exists(plate_data->pick_file))) {
                    BOOST_LOG_TRIVIAL(info) << boost::format("thumbnails stage: plate %1%'s top_file %2% also not there, need to regenerate")%(i+1)%plate_data->top_file;
                    if (!skip_this_plate) {
                        need_regenerate_top_thumbnail = true;
                        need_create_top_group = true;
                    }
                }
                else {
                    BOOST_LOG_TRIVIAL(info) << boost::format("thumbnails stage: plate %1%'s top_thumbnail file exists, no need to regenerate")%(i+1);
                }
            }
        }

        if (need_regenerate_thumbnail || need_regenerate_top_thumbnail) {
            std::vector<std::string> colors;
            if (filament_color) {
                colors= filament_color->vserialize();
            }
            else
                colors.push_back("#FFFFFF");

            std::vector<std::array<float, 4>> colors_out(colors.size());
            unsigned char rgb_color[3] = {};
            for (const std::string& color : colors) {
                Slic3r::GUI::BitmapCache::parse_color(color, rgb_color);
                size_t color_idx = &color - &colors.front();
                colors_out[color_idx] = { float(rgb_color[0]) / 255.f, float(rgb_color[1]) / 255.f, float(rgb_color[2]) / 255.f, 1.f };
            }

            int gl_major, gl_minor, gl_verbos;
            glfwGetVersion(&gl_major, &gl_minor, &gl_verbos);
            BOOST_LOG_TRIVIAL(info) << boost::format("opengl version %1%.%2%.%3%")%gl_major %gl_minor %gl_verbos;

            glfwSetErrorCallback(glfw_callback);
            int ret = glfwInit();
            if (ret == GLFW_FALSE) {
                int code = glfwGetError(NULL);
                BOOST_LOG_TRIVIAL(error) << "glfwInit return error, code " <<code<< std::endl;
            }
            else {
                BOOST_LOG_TRIVIAL(info) << "glfwInit Success."<< std::endl;
                glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, gl_major);
                glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, gl_minor);
                glfwWindowHint(GLFW_RED_BITS, 8);
                glfwWindowHint(GLFW_GREEN_BITS, 8);
                glfwWindowHint(GLFW_BLUE_BITS, 8);
                glfwWindowHint(GLFW_ALPHA_BITS, 8);
                glfwWindowHint(GLFW_VISIBLE, false);
                //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
                //glfwDisable(GLFW_AUTO_POLL_EVENTS);
#ifdef __WXMAC__
                glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
                glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#else
                glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);
#endif

#ifdef __linux__
                glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_OSMESA_CONTEXT_API);
#endif

                GLFWwindow* window = glfwCreateWindow(640, 480, "base_window", NULL, NULL);
                if (window == NULL)
                {
                    BOOST_LOG_TRIVIAL(error) << "Failed to create GLFW window" << std::endl;
                }
                else
                    glfwMakeContextCurrent(window);
            }

            //opengl manager related logic
            {
                Slic3r::GUI::OpenGLManager opengl_mgr;
                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, false, 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);
                                glvolume_collection.volumes.back()->printable = model_instance.printable;
                            }
                        }
                    }

                    ThumbnailsParams thumbnail_params;
                    GLShaderProgram* shader = opengl_mgr.get_shader("thumbnail");
                    if (!shader) {
                        BOOST_LOG_TRIVIAL(error) << boost::format("can not get shader for rendering thumbnail");
                    }
                    else {
                        for (int i = 0; i < partplate_list.get_plate_count(); i++) {
                            Slic3r::GUI::PartPlate *part_plate      = partplate_list.get_plate(i);
                            PlateData *plate_data = plate_data_list[i];
                            if (plate_data->plate_thumbnail.is_valid()) {
                                if ((plate_to_slice != 0) && (plate_to_slice != (i + 1))) {
                                    BOOST_LOG_TRIVIAL(info) << boost::format("Line %1%: regenerate thumbnail, reset plate %2%'s thumbnail.")%__LINE__%(i+1);
                                    plate_data->plate_thumbnail.reset();
                                }
                                else
                                    BOOST_LOG_TRIVIAL(info) << boost::format("plate %1% has a valid thumbnail, width %2%, height %3%， directly using it")%(i+1) %plate_data->plate_thumbnail.width %plate_data->plate_thumbnail.height;
                            }
                            else if (!plate_data->thumbnail_file.empty() && (boost::filesystem::exists(plate_data->thumbnail_file)))
                            {
                                if ((plate_to_slice != 0) && (plate_to_slice != (i + 1))) {
                                    BOOST_LOG_TRIVIAL(info) << boost::format("Line %1%: regenerate thumbnail, clear plate %2%'s thumbnail file path to empty.")%__LINE__%(i+1);
                                    plate_data->thumbnail_file.clear();
                                }
                                else
                                    BOOST_LOG_TRIVIAL(info) << boost::format("plate %1% has a valid thumbnail %2% extracted from 3mf, directly using it")%(i+1) %plate_data->thumbnail_file;
                            }
                            else {
                                ThumbnailData* thumbnail_data = &plate_data->plate_thumbnail;

                                if ((plate_to_slice != 0) && (plate_to_slice != (i + 1))) {
                                    BOOST_LOG_TRIVIAL(info) << boost::format("Line %1%: regenerate thumbnail, Skip plate %2%.")%__LINE__%(i+1);
                                }
                                else {
                                    unsigned int thumbnail_width = 512, thumbnail_height = 512;
                                    const ThumbnailsParams thumbnail_params = {{}, false, true, true, true, i};

                                    BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%'s thumbnail, need to regenerate")%(i+1);
                                    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;
                                    }
                                    BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%'s thumbnail,finished rendering")%(i+1);
                                }
                            }
                            if (need_create_thumbnail_group) {
                                thumbnails.push_back(&plate_data->plate_thumbnail);
                                BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%: add thumbnail data into group")%(i+1);
                            }

                            //top thumbnails
                            /*if (part_plate->top_thumbnail_data.is_valid() && part_plate->pick_thumbnail_data.is_valid()) {
                                if ((plate_to_slice != 0) && (plate_to_slice != (i + 1))) {
                                    BOOST_LOG_TRIVIAL(info) << boost::format("Line %1%: regenerate thumbnail, reset plate %2%'s top/pick thumbnail.")%__LINE__%(i+1);
                                    part_plate->top_thumbnail_data.reset();
                                    part_plate->pick_thumbnail_data.reset();
                                    plate_data->top_file.clear();
                                    plate_data->pick_file.clear();
                                }
                                else {
                                    plate_data->top_file = "valid_top";
                                    plate_data->pick_file = "valid_pick";
                                    BOOST_LOG_TRIVIAL(info) << boost::format("plate %1% has a valid top/pick thumbnail data, directly using it")%(i+1);
                                }
                            }
                            else*/
                            if ((!plate_data->top_file.empty() && (boost::filesystem::exists(plate_data->top_file)))
                                &&(!plate_data->pick_file.empty() && (boost::filesystem::exists(plate_data->pick_file))))
                            {
                                if ((plate_to_slice != 0) && (plate_to_slice != (i + 1))) {
                                    BOOST_LOG_TRIVIAL(info) << boost::format("Line %1%: regenerate thumbnail, clear plate %2%'s top/pick thumbnail file path to empty.")%__LINE__%(i+1);
                                    plate_data->top_file.clear();
                                    plate_data->pick_file.clear();
                                }
                                else
                                    BOOST_LOG_TRIVIAL(info) << boost::format("plate %1% has valid top/pick thumbnail extracted from 3mf, directly using it")%(i+1);
                            }
                            else{
                                ThumbnailData* top_thumbnail = &part_plate->top_thumbnail_data;
                                ThumbnailData* picking_thumbnail = &part_plate->pick_thumbnail_data;
                                if ((plate_to_slice != 0) && (plate_to_slice != (i + 1))) {
                                    BOOST_LOG_TRIVIAL(info) << boost::format("Line %1%: regenerate thumbnail, Skip plate %2%.")%__LINE__%(i+1);
                                    part_plate->top_thumbnail_data.reset();
                                    part_plate->pick_thumbnail_data.reset();
                                    plate_data->top_file.clear();
                                    plate_data->pick_file.clear();
                                }
                                else {
                                    unsigned int thumbnail_width = 512, thumbnail_height = 512;
                                    const ThumbnailsParams thumbnail_params = { {}, false, true, false, true, i };

                                    BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%'s top/pick thumbnail missed, need to regenerate")%(i+1);

                                    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(*top_thumbnail,
                                                   thumbnail_width, thumbnail_height, thumbnail_params,
                                                   partplate_list, model.objects, glvolume_collection, colors_out, shader, Slic3r::GUI::Camera::EType::Ortho, true, false);
                                                Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer(*picking_thumbnail,
                                                   thumbnail_width, thumbnail_height, thumbnail_params,
                                                   partplate_list, model.objects, glvolume_collection, colors_out, shader, Slic3r::GUI::Camera::EType::Ortho, true, true);
                                                break;
                                            }
                                    case Slic3r::GUI::OpenGLManager::EFramebufferType::Ext:
                                            {
                                                BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: EXT");
                                                Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer_ext(*top_thumbnail,
                                                   thumbnail_width, thumbnail_height, thumbnail_params,
                                                   partplate_list, model.objects, glvolume_collection, colors_out, shader, Slic3r::GUI::Camera::EType::Ortho, true, false);
                                                Slic3r::GUI::GLCanvas3D::render_thumbnail_framebuffer_ext(*picking_thumbnail,
                                                   thumbnail_width, thumbnail_height, thumbnail_params,
                                                   partplate_list, model.objects, glvolume_collection, colors_out, shader, Slic3r::GUI::Camera::EType::Ortho, true, true);
                                                break;
                                            }
                                    default:
                                            BOOST_LOG_TRIVIAL(info) << boost::format("framebuffer_type: unknown");
                                            break;
                                    }
                                    plate_data->top_file = "valid_top";
                                    plate_data->pick_file = "valid_pick";
                                    BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%'s top_thumbnail,finished rendering")%(i+1);
                                }
                            }

                            if (need_create_top_group) {
                                top_thumbnails.push_back(&part_plate->top_thumbnail_data);
                                pick_thumbnails.push_back(&part_plate->pick_thumbnail_data);
                                BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%: add thumbnail data for top and pick into group")%(i+1);
                            }
                        }
                    }
                }
            }
            //BBS: release glfw
            glfwTerminate();
        }
        else {
            BOOST_LOG_TRIVIAL(info) << boost::format("use previous thumbnails, no need to regenerate");
            for (int i = 0; i < partplate_list.get_plate_count(); i++) {
                PlateData *plate_data = plate_data_list[i];
                bool skip_this_plate = ((plate_to_slice != 0) && (plate_to_slice != (i + 1)))?true:false;
                Slic3r::GUI::PartPlate *part_plate      = partplate_list.get_plate(i);

                if (skip_this_plate) {
                    BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%'s all the thumbnails skipped, reset here")%(i+1);
                    plate_data->plate_thumbnail.reset();
                    plate_data->thumbnail_file.clear();
                    part_plate->top_thumbnail_data.reset();
                    part_plate->pick_thumbnail_data.reset();
                    plate_data->top_file.clear();
                    plate_data->pick_file.clear();
                }

                if (need_create_thumbnail_group) {
                    thumbnails.push_back(&plate_data->plate_thumbnail);
                    BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%: add thumbnail data into group")%(i+1);
                }

                if (need_create_top_group) {
                    top_thumbnails.push_back(&part_plate->top_thumbnail_data);
                    pick_thumbnails.push_back(&part_plate->pick_thumbnail_data);
                    BOOST_LOG_TRIVIAL(info) << boost::format("plate %1%: add thumbnail data for top and pick into group")%(i+1);
                }
            }
        }

        //generate first layer bboxes
        for (int i = 0; i < partplate_list.get_plate_count(); i++) {
            if ((plate_to_slice != 0) && (plate_to_slice != (i + 1))) {
                BOOST_LOG_TRIVIAL(info) << boost::format("Line %1%: generate bbox, Skip plate %2%.")%__LINE__%(i+1);
                plate_bboxes.push_back(new PlateBBoxData());
                continue;
            }
            Slic3r::GUI::PartPlate *part_plate      = partplate_list.get_plate(i);
            //render calibration thumbnail
            if (!part_plate->get_slice_result() || !part_plate->is_slice_result_valid()) {
                BOOST_LOG_TRIVIAL(info) << boost::format("plate %1% doesn't have a valid sliced result, skip it")%(i+1);
                //calibration_thumbnails.push_back(new ThumbnailData());
                plate_bboxes.push_back(new PlateBBoxData());
                continue;
            }
            PrintBase  *print_base=NULL;
            Slic3r::GUI::GCodeResult *gcode_result = NULL;
            int print_index;
            part_plate->get_print(&print_base, &gcode_result, &print_index);

            Print *print = dynamic_cast<Print *>(print_base);

             //don't render calibration picture
            /*BuildVolume build_volume(part_plate->get_shape(), print_height);
            const std::vector<BoundingBoxf3>& exclude_bounding_box = part_plate->get_exclude_areas();
            Slic3r::GUI::GCodeViewer gcode_viewer;
            gcode_viewer.init(ConfigOptionMode::comAdvanced, nullptr);
            gcode_viewer.load(*gcode_result, *print, build_volume, exclude_bounding_box, false, ConfigOptionMode::comAdvanced, false);

            std::vector<std::string> colors;
            if (filament_color)
                colors = filament_color->values;
            gcode_viewer.refresh(*gcode_result, colors);

            ThumbnailData* calibration_data = new ThumbnailData();
            const ThumbnailsParams calibration_params = { {}, false, true, true, true, i };
            //BBS fixed size
            const int cali_thumbnail_width = 2560;
            const int cali_thumbnail_height = 2560;
            gcode_viewer.render_calibration_thumbnail(*calibration_data, cali_thumbnail_width, cali_thumbnail_height,
                calibration_params, partplate_list, opengl_mgr);
            //generate_calibration_thumbnail(*calibration_data, thumbnail_width, thumbnail_height, calibration_params);
            //*plate_bboxes[index] = p->generate_first_layer_bbox();
            calibration_thumbnails.push_back(calibration_data);*/

            PlateBBoxData* plate_bbox = new PlateBBoxData();
            std::vector<BBoxData>& id_bboxes = plate_bbox->bbox_objs;
            BoundingBoxf bbox_all;
            PrintSequence curr_plate_seq = part_plate->get_print_seq();
            if (curr_plate_seq == PrintSequence::ByDefault) {
                auto seq_print  = m_print_config.option<ConfigOptionEnum<PrintSequence>>("print_sequence");
                if (seq_print && (seq_print->value == PrintSequence::ByObject)) {
                    BOOST_LOG_TRIVIAL(info) << boost::format("plate %1% print by object, set from global")%(i+1);
                    plate_bbox->is_seq_print = true;
                }
            }
            else if (curr_plate_seq == PrintSequence::ByObject) {
                BOOST_LOG_TRIVIAL(info) << boost::format("plate %1% print by object, set from plate self")%(i+1);
                plate_bbox->is_seq_print = true;
            }
            plate_bbox->first_extruder = print->get_tool_ordering().first_extruder();
            //bed type;
            BedType plate_bed_type = part_plate->get_bed_type();
            if (plate_bed_type == btDefault) {
                auto cur_bed_type  = m_print_config.option<ConfigOptionEnum<BedType>>("curr_bed_type");
                if (cur_bed_type) {
                    BOOST_LOG_TRIVIAL(info) << boost::format("plate %1% bed type: %2%, set from global")%(i+1) %cur_bed_type->serialize();
                    plate_bbox->bed_type = bed_type_to_gcode_string(cur_bed_type->value);
                }
            }
            else {
                BOOST_LOG_TRIVIAL(info) << boost::format("plate %1% bed type: %2%, set from plate self")%(i+1) %plate_bed_type;
                plate_bbox->bed_type       = bed_type_to_gcode_string(plate_bed_type);
            }
            // get nozzle diameter
            auto opt_nozzle_diameters = m_print_config.option<ConfigOptionFloats>("nozzle_diameter");
            if (opt_nozzle_diameters != nullptr)
                plate_bbox->nozzle_diameter = float(opt_nozzle_diameters->get_at(plate_bbox->first_extruder));

            auto objects = print->objects();
            auto orig = part_plate->get_origin();
            Vec2d orig2d = { orig[0], orig[1] };

            for (auto obj : objects)
            {
                BBoxData data;
                auto bb_scaled = obj->get_first_layer_bbox(data.area, data.layer_height, data.name);
                auto bb = unscaled(bb_scaled);
                bb.min -= orig2d;
                bb.max -= orig2d;
                bbox_all.merge(bb);
                data.area *= (SCALING_FACTOR * SCALING_FACTOR); // unscale area
                data.id = obj->id().id;
                data.bbox = { bb.min.x(),bb.min.y(),bb.max.x(),bb.max.y() };
                id_bboxes.emplace_back(std::move(data));
            }
            // add wipe tower bounding box
            if (print->has_wipe_tower()) {
                BBoxData data;
                auto   wt_corners = print->first_layer_wipe_tower_corners();
                // when loading gcode.3mf, wipe tower info may not be correct
                if (!wt_corners.empty()) {
                    BoundingBox bb_scaled = {wt_corners[0], wt_corners[2]};
                    auto        bb        = unscaled(bb_scaled);
                    bb.min -= orig2d;
                    bb.max -= orig2d;
                    bbox_all.merge(bb);
                    data.name = "wipe_tower";
                    data.id   = partplate_list.get_curr_plate()->get_index() + 1000;
                    data.bbox = {bb.min.x(), bb.min.y(), bb.max.x(), bb.max.y()};
                    id_bboxes.emplace_back(std::move(data));
                }
            }
            plate_bbox->bbox_all = { bbox_all.min.x(),bbox_all.min.y(),bbox_all.max.x(),bbox_all.max.y() };

            PlateData *plate_data = plate_data_list[i];
            for (auto it = plate_data->slice_filaments_info.begin(); it != plate_data->slice_filaments_info.end(); it++) {
                plate_bbox->filament_ids.push_back(it->id);
                plate_bbox->filament_colors.push_back(it->color);
            }
            plate_bboxes.push_back(plate_bbox);
        }


#if defined(__linux__) || defined(__LINUX__)
        if (g_cli_callback_mgr.is_started()) {
            PrintBase::SlicingStatus slicing_status{97, "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, top_thumbnails, pick_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++)
            thumbnails[i]->reset();
        for (unsigned int i = 0; i < top_thumbnails.size(); i++)
            top_thumbnails[i]->reset();
        for (unsigned int i = 0; i < pick_thumbnails.size(); i++)
            pick_thumbnails[i]->reset();

        for (unsigned int i = 0; i < calibration_thumbnails.size(); i++)
            delete calibration_thumbnails[i];

        for (int i = 0; i < plate_bboxes.size(); i++)
            delete plate_bboxes[i];
    }

    if (plate_data_src.size() > 0)
    {
        release_PlateData_list(plate_data_src);
    }

#if defined(__linux__) || defined(__LINUX__)
    if (g_cli_callback_mgr.is_started()) {
        PrintBase::SlicingStatus slicing_status{100, "All done, Success"};
        cli_status_callback(slicing_status);
    }

    g_cli_callback_mgr.stop();
#endif

    for (Model &model : m_models) {
	model.remove_backup_path_if_exist();
    }
    //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)
{
    // Detect the operating system flavor after SLIC3R_LOGLEVEL is set.
    detect_platform();

#ifdef WIN32
    // Notify user that a blacklisted DLL was injected into OrcaSlicer process (for example Nahimic, see GH #5573).
    // We hope that if a DLL is being injected into a OrcaSlicer 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 OrcaSlicer process:\n\n";
        text += BlacklistedLibraryCheck::get_instance().get_blacklisted_string();
        text += L"\n\n"
                L"OrcaSlicer 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 OrcaSlicer.\n"
                L"For example, ASUS Sonic Studio injects a Nahimic driver, which makes OrcaSlicer "
                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().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().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);
    }

    //FIXME Validating at this stage most likely does not make sense, as the config is not fully initialized yet.
    std::map<std::string, std::string> validity = m_config.validate(true);

    // 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 << "Params in command line error: "<< std::endl;
        for (std::map<std::string, std::string>::iterator it=validity.begin(); it!=validity.end(); ++it)
            boost::nowide::cerr << it->first <<": "<< it->second << std::endl;
        return false;
    }

    return true;
}

void CLI::print_help(bool include_print_options, PrinterTechnology printer_technology) const
{
    boost::nowide::cout
        << SLIC3R_APP_KEY <<"-"<< SoftFever_VERSION << ":"
        << std::endl
        << "Usage: orca-slicer [ 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*>& top_thumbnails, std::vector<ThumbnailData*>& pick_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.top_thumbnail_data = top_thumbnails;
    store_params.pick_thumbnail_data = pick_thumbnails;
    store_params.calibration_thumbnail_data = calibration_thumbnails;
    store_params.id_bboxes = plate_bboxes;
    store_params.strategy = SaveStrategy::Silence|SaveStrategy::WithGcode|SaveStrategy::SplitModel|SaveStrategy::UseLoadedId;

    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 orcaslicer_main(int argc, wchar_t **argv)
    {
        // Convert wchar_t arguments to UTF8.
        std::vector<std::string> 	argv_narrow;
        std::vector<char*>			argv_ptrs(argc + 1, nullptr);
        for (size_t i = 0; i < argc; ++ i)
            argv_narrow.emplace_back(boost::nowide::narrow(argv[i]));
        for (size_t i = 0; i < argc; ++ i)
            argv_ptrs[i] = argv_narrow[i].data();

//BBS: register default exception handler
#if BBL_RELEASE_TO_PUBLIC
        SET_DEFULTER_HANDLER();
#else
        AddVectoredExceptionHandler(1, CBaseException::UnhandledExceptionFilter);
#endif
        // Call the UTF8 main.
        return CLI().run(argc, argv_ptrs.data());
    }
}
#else /* _MSC_VER */
int main(int argc, char **argv)
{
    return CLI().run(argc, argv);
}
#endif /* _MSC_VER */
