mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-11-02 20:51:23 -07:00 
			
		
		
		
	Merge branch 'master-remote' into feature/1.5
Signed-off-by: SoftFever <softfeverever@gmail.com> # Conflicts: # bbl/i18n/BambuStudio.pot # bbl/i18n/de/BambuStudio_de.po # bbl/i18n/en/BambuStudio_en.po # bbl/i18n/es/BambuStudio_es.po # bbl/i18n/fr/BambuStudio_fr.po # bbl/i18n/hu/BambuStudio_hu.po # bbl/i18n/it/BambuStudio_it.po # bbl/i18n/nl/BambuStudio_nl.po # bbl/i18n/sv/BambuStudio_sv.po # bbl/i18n/zh_cn/BambuStudio_zh_CN.po # deps/Boost/Boost.cmake # deps/wxWidgets/wxWidgets.cmake # resources/config.json # resources/i18n/de/BambuStudio.mo # resources/i18n/en/BambuStudio.mo # resources/i18n/es/BambuStudio.mo # resources/i18n/fr/BambuStudio.mo # resources/i18n/hu/BambuStudio.mo # resources/i18n/it/BambuStudio.mo # resources/i18n/nl/BambuStudio.mo # resources/i18n/sv/BambuStudio.mo # resources/i18n/zh_cn/BambuStudio.mo # resources/images/tips_arrow.svg # resources/profiles/Anycubic.json # resources/profiles/Anycubic/filament/Anycubic Generic ABS.json # resources/profiles/Anycubic/filament/Anycubic Generic ASA.json # resources/profiles/Anycubic/filament/Anycubic Generic PA-CF.json # resources/profiles/Anycubic/filament/Anycubic Generic PA.json # resources/profiles/Anycubic/filament/Anycubic Generic PC.json # resources/profiles/Anycubic/filament/Anycubic Generic PETG.json # resources/profiles/Anycubic/filament/Anycubic Generic PLA-CF.json # resources/profiles/Anycubic/filament/Anycubic Generic PLA.json # resources/profiles/Anycubic/filament/Anycubic Generic PVA.json # resources/profiles/Anycubic/filament/Anycubic Generic TPU.json # resources/profiles/Anycubic/filament/fdm_filament_common.json # resources/profiles/Anycubic/machine/Anycubic 4Max Pro 0.4 nozzle.json # resources/profiles/Anycubic/machine/Anycubic 4Max Pro.json # resources/profiles/Anycubic/process/0.20mm Standard @4MaxPro.json # resources/profiles/Anycubic/process/fdm_process_common.json # resources/profiles/BBL.json # resources/profiles/BBL/machine/Bambu Lab P1P 0.2 nozzle.json # resources/profiles/BBL/machine/Bambu Lab P1P 0.4 nozzle.json # resources/profiles/BBL/machine/Bambu Lab P1P 0.6 nozzle.json # resources/profiles/BBL/machine/Bambu Lab P1P 0.8 nozzle.json # resources/profiles/BBL/machine/Bambu Lab X1 0.2 nozzle.json # resources/profiles/BBL/machine/Bambu Lab X1 0.4 nozzle.json # resources/profiles/BBL/machine/Bambu Lab X1 0.6 nozzle.json # resources/profiles/BBL/machine/Bambu Lab X1 0.8 nozzle.json # resources/profiles/BBL/machine/Bambu Lab X1 Carbon 0.2 nozzle.json # resources/profiles/BBL/machine/Bambu Lab X1 Carbon 0.4 nozzle.json # resources/profiles/BBL/machine/Bambu Lab X1 Carbon 0.6 nozzle.json # resources/profiles/BBL/machine/Bambu Lab X1 Carbon 0.8 nozzle.json # resources/profiles/BBL/machine/fdm_bbl_3dp_001_common.json # resources/profiles/Voron.json # resources/web/data/text.js # resources/web/image/printer/Anycubic 4Max Pro_cover.png # src/BambuStudio.cpp # src/libslic3r/GCode.cpp # src/libslic3r/GCode.hpp # src/libslic3r/GCode/GCodeProcessor.cpp # src/libslic3r/GCodeWriter.hpp # src/libslic3r/PerimeterGenerator.cpp # src/libslic3r/PresetBundle.cpp # src/libslic3r/Print.cpp # src/libslic3r/Print.hpp # src/libslic3r/PrintConfig.cpp # src/libslic3r/PrintConfig.hpp # src/libslic3r/PrintObject.cpp # src/slic3r/GUI/AMSMaterialsSetting.cpp # src/slic3r/GUI/AMSMaterialsSetting.hpp # src/slic3r/GUI/AmsMappingPopup.cpp # src/slic3r/GUI/AmsMappingPopup.hpp # src/slic3r/GUI/Auxiliary.cpp # src/slic3r/GUI/BackgroundSlicingProcess.cpp # src/slic3r/GUI/ConfigManipulation.cpp # src/slic3r/GUI/DeviceManager.cpp # src/slic3r/GUI/DeviceManager.hpp # src/slic3r/GUI/ExtrusionCalibration.cpp # src/slic3r/GUI/GCodeViewer.cpp # src/slic3r/GUI/GCodeViewer.hpp # src/slic3r/GUI/GUI_App.cpp # src/slic3r/GUI/IMSlider.cpp # src/slic3r/GUI/Jobs/PrintJob.cpp # src/slic3r/GUI/Jobs/PrintJob.hpp # src/slic3r/GUI/Jobs/SendJob.cpp # src/slic3r/GUI/Jobs/SendJob.hpp # src/slic3r/GUI/MainFrame.cpp # src/slic3r/GUI/MainFrame.hpp # src/slic3r/GUI/MediaPlayCtrl.cpp # src/slic3r/GUI/OptionsGroup.cpp # src/slic3r/GUI/PhysicalPrinterDialog.cpp # src/slic3r/GUI/Plater.cpp # src/slic3r/GUI/PrintHostDialogs.cpp # src/slic3r/GUI/Printer/BambuTunnel.h # src/slic3r/GUI/Printer/PrinterFileSystem.cpp # src/slic3r/GUI/Printer/gstbambusrc.c # src/slic3r/GUI/Printer/gstbambusrc.h # src/slic3r/GUI/ReleaseNote.cpp # src/slic3r/GUI/ReleaseNote.hpp # src/slic3r/GUI/SelectMachine.cpp # src/slic3r/GUI/SendToPrinter.cpp # src/slic3r/GUI/SetBedTypeDialog.cpp # src/slic3r/GUI/StatusPanel.cpp # src/slic3r/GUI/StatusPanel.hpp # src/slic3r/GUI/Tab.cpp # src/slic3r/GUI/Widgets/AMSControl.cpp # src/slic3r/GUI/Widgets/AMSControl.hpp # src/slic3r/GUI/Widgets/ImageSwitchButton.cpp # src/slic3r/GUI/Widgets/Label.cpp # src/slic3r/GUI/WipeTowerDialog.cpp # src/slic3r/Utils/Process.cpp # src/slic3r/Utils/bambu_networking.hpp # version.inc
This commit is contained in:
		
						commit
						5ef51f6c8a
					
				
					 339 changed files with 37169 additions and 5445 deletions
				
			
		| 
						 | 
				
			
			@ -324,6 +324,11 @@ 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -334,7 +339,7 @@ int CLI::run(int argc, char **argv)
 | 
			
		|||
    // 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();
 | 
			
		||||
| 
						 | 
				
			
			@ -360,17 +365,17 @@ int CLI::run(int argc, char **argv)
 | 
			
		|||
    /*BOOST_LOG_TRIVIAL(info) << "begin to setup params, argc=" << argc << std::endl;
 | 
			
		||||
    for (int index=0; index < argc; index++)
 | 
			
		||||
        BOOST_LOG_TRIVIAL(info) << "index="<< index <<", arg is "<< argv[index] <<std::endl;
 | 
			
		||||
    int debug_argc = 9;
 | 
			
		||||
    int debug_argc = 5;
 | 
			
		||||
    char *debug_argv[] = {
 | 
			
		||||
        "E:\work\projects\bambu_release\bamboo_slicer\build_debug\src\Debug\bambu-studio.exe",
 | 
			
		||||
        "--slice",
 | 
			
		||||
        "0",
 | 
			
		||||
        "--load-settings",
 | 
			
		||||
        "machine.json;process.json",
 | 
			
		||||
        "--load-filaments",
 | 
			
		||||
        "filament.json",
 | 
			
		||||
        "9",
 | 
			
		||||
        //"--load-settings",
 | 
			
		||||
        //"machine.json;process.json",
 | 
			
		||||
        //"--load-filaments",
 | 
			
		||||
        //"filament.json",
 | 
			
		||||
        "--export-3mf=output.3mf",
 | 
			
		||||
        "boat.stl"
 | 
			
		||||
        "test_outside.3mf"
 | 
			
		||||
        };
 | 
			
		||||
    if (! this->setup(debug_argc, debug_argv))*/
 | 
			
		||||
    if (!this->setup(argc, argv))
 | 
			
		||||
| 
						 | 
				
			
			@ -398,6 +403,8 @@ int CLI::run(int argc, char **argv)
 | 
			
		|||
            boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer");
 | 
			
		||||
#endif // _WIN32*/
 | 
			
		||||
 | 
			
		||||
    bool translate_old = 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;
 | 
			
		||||
| 
						 | 
				
			
			@ -457,6 +464,15 @@ int CLI::run(int argc, char **argv)
 | 
			
		|||
        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;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -531,6 +547,12 @@ int CLI::run(int argc, char **argv)
 | 
			
		|||
                        BOOST_LOG_TRIVIAL(info) << "object "<<o->name <<", id :" << o->id().id << ", from bbl 3mf\n";
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    Semver old_version(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();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    /*for (ModelObject *model_object : model.objects)
 | 
			
		||||
                        for (ModelInstance *model_instance : model_object->instances)
 | 
			
		||||
                        {
 | 
			
		||||
| 
						 | 
				
			
			@ -991,6 +1013,7 @@ int CLI::run(int argc, char **argv)
 | 
			
		|||
        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);
 | 
			
		||||
| 
						 | 
				
			
			@ -1035,6 +1058,7 @@ int CLI::run(int argc, char **argv)
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
| 
						 | 
				
			
			@ -1072,6 +1096,7 @@ int CLI::run(int argc, char **argv)
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
| 
						 | 
				
			
			@ -1137,9 +1162,11 @@ int CLI::run(int argc, char **argv)
 | 
			
		|||
            //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.
 | 
			
		||||
| 
						 | 
				
			
			@ -1149,7 +1176,7 @@ int CLI::run(int argc, char **argv)
 | 
			
		|||
                const ConfigOption *source_opt = config.option(opt_key);
 | 
			
		||||
                if (source_opt == nullptr) {
 | 
			
		||||
                    // The key was not found in the source config, therefore it will not be initialized!
 | 
			
		||||
                    boost::nowide::cerr << "can not found option " <<opt_key<<"from filament file "<< load_filaments[filament_index -1] <<std::endl;
 | 
			
		||||
                    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()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1169,7 +1196,7 @@ int CLI::run(int argc, char **argv)
 | 
			
		|||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        //skip the scalar values
 | 
			
		||||
                        BOOST_LOG_TRIVIAL(warning) << "skip scalar option " <<opt_key<<" from filament file "<< load_filaments[filament_index -1] <<std::endl;
 | 
			
		||||
                        BOOST_LOG_TRIVIAL(info) << boost::format("skip scalar option %1% from filament %2%: %3%")%opt_key%filament_index%load_filaments_name[index];;
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			@ -1179,7 +1206,7 @@ int CLI::run(int argc, char **argv)
 | 
			
		|||
                    if (opt == nullptr) {
 | 
			
		||||
                        // opt_key does not exist in this ConfigBase and it cannot be created, because it is not defined by this->def().
 | 
			
		||||
                        // This is only possible if other is of DynamicConfig type.
 | 
			
		||||
                        boost::nowide::cerr << "can not create option " <<opt_key<<"to config, from filament file "<< load_filaments[filament_index -1] <<std::endl;
 | 
			
		||||
                        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);
 | 
			
		||||
| 
						 | 
				
			
			@ -1250,10 +1277,12 @@ int CLI::run(int argc, char **argv)
 | 
			
		|||
        m_print_config.apply(sla_print_config, true);*/
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string validity = m_print_config.validate();
 | 
			
		||||
    std::map<std::string, std::string> validity = m_print_config.validate(true);
 | 
			
		||||
    if (!validity.empty()) {
 | 
			
		||||
        boost::nowide::cerr <<"Error: The composite configation is not valid: " << validity << std::endl;
 | 
			
		||||
        flush_and_exit(CLI_INVALID_PRINTER_TECH);
 | 
			
		||||
        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
 | 
			
		||||
| 
						 | 
				
			
			@ -1266,10 +1295,19 @@ int CLI::run(int argc, char **argv)
 | 
			
		|||
    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)
 | 
			
		||||
    {
 | 
			
		||||
        std::string bed_texture;
 | 
			
		||||
        partplate_list.reset_size(bedfs[2].x() - bedfs[0].x(), bedfs[2].y() - bedfs[0].y(), print_height);
 | 
			
		||||
        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";
 | 
			
		||||
| 
						 | 
				
			
			@ -1277,6 +1315,21 @@ int CLI::run(int argc, char **argv)
 | 
			
		|||
    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)
 | 
			
		||||
| 
						 | 
				
			
			@ -1840,6 +1893,7 @@ int CLI::run(int argc, char **argv)
 | 
			
		|||
                    }*/
 | 
			
		||||
                    DynamicPrintConfig new_print_config = m_print_config;
 | 
			
		||||
                    new_print_config.apply(*part_plate->config());
 | 
			
		||||
                    new_print_config.apply(m_extra_config, true);
 | 
			
		||||
                    print->apply(model, new_print_config);
 | 
			
		||||
                    StringObjectException warning;
 | 
			
		||||
                    auto err = print->validate(&warning);
 | 
			
		||||
| 
						 | 
				
			
			@ -2326,17 +2380,6 @@ int CLI::run(int argc, char **argv)
 | 
			
		|||
 | 
			
		||||
bool CLI::setup(int argc, char **argv)
 | 
			
		||||
{
 | 
			
		||||
    {
 | 
			
		||||
	    Slic3r::set_logging_level(1);
 | 
			
		||||
        const char *loglevel = boost::nowide::getenv("BBL_LOGLEVEL");
 | 
			
		||||
        if (loglevel != nullptr) {
 | 
			
		||||
            if (loglevel[0] >= '0' && loglevel[0] <= '9' && loglevel[1] == 0)
 | 
			
		||||
                set_logging_level(loglevel[0] - '0');
 | 
			
		||||
            else
 | 
			
		||||
                boost::nowide::cerr << "Invalid BBL_LOGLEVEL environment variable: " << loglevel << std::endl;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Detect the operating system flavor after SLIC3R_LOGLEVEL is set.
 | 
			
		||||
    detect_platform();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2365,7 +2408,7 @@ bool CLI::setup(int argc, char **argv)
 | 
			
		|||
#ifdef __APPLE__
 | 
			
		||||
    // The application is packed in the .dmg archive as 'Slic3r.app/Contents/MacOS/Slic3r'
 | 
			
		||||
    // The resources are packed to 'Slic3r.app/Contents/Resources'
 | 
			
		||||
    boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path() / "../Resources";
 | 
			
		||||
    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'
 | 
			
		||||
| 
						 | 
				
			
			@ -2379,7 +2422,7 @@ bool CLI::setup(int argc, char **argv)
 | 
			
		|||
    // The application is packed in the .tar.bz archive (or in AppImage) as 'bin/slic3r',
 | 
			
		||||
    // The resources are packed to 'resources'
 | 
			
		||||
    // Path from Slic3r binary to resources:
 | 
			
		||||
    boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path() / "../resources";
 | 
			
		||||
    boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path().parent_path() / "resources";
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    set_resources_dir(path_resources.string());
 | 
			
		||||
| 
						 | 
				
			
			@ -2404,17 +2447,8 @@ bool CLI::setup(int argc, char **argv)
 | 
			
		|||
            m_transforms.emplace_back(opt_key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#if !BBL_RELEASE_TO_PUBLIC
 | 
			
		||||
    {
 | 
			
		||||
        const ConfigOptionInt *opt_loglevel = m_config.opt<ConfigOptionInt>("debug");
 | 
			
		||||
        if (opt_loglevel != 0) {
 | 
			
		||||
            set_logging_level(opt_loglevel->value);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    //FIXME Validating at this stage most likely does not make sense, as the config is not fully initialized yet.
 | 
			
		||||
    std::string validity = m_config.validate();
 | 
			
		||||
    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 })
 | 
			
		||||
| 
						 | 
				
			
			@ -2425,7 +2459,9 @@ bool CLI::setup(int argc, char **argv)
 | 
			
		|||
 | 
			
		||||
    //FIXME Validating at this stage most likely does not make sense, as the config is not fully initialized yet.
 | 
			
		||||
    if (!validity.empty()) {
 | 
			
		||||
        boost::nowide::cerr << "error: " << validity << std::endl;
 | 
			
		||||
        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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2608,7 +2644,7 @@ extern "C" {
 | 
			
		|||
            argv_ptrs[i] = argv_narrow[i].data();
 | 
			
		||||
 | 
			
		||||
//BBS: register default exception handler
 | 
			
		||||
#if 1
 | 
			
		||||
#if BBL_RELEASE_TO_PUBLIC
 | 
			
		||||
        SET_DEFULTER_HANDLER();
 | 
			
		||||
#else
 | 
			
		||||
        AddVectoredExceptionHandler(1, CBaseException::UnhandledExceptionFilter);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -186,6 +186,9 @@ namespace ImGui
 | 
			
		|||
    const wchar_t SphereButtonDarkIcon     = 0x0826;
 | 
			
		||||
    const wchar_t GapFillDarkIcon          = 0x0827;
 | 
			
		||||
 | 
			
		||||
    const wchar_t TextSearchIcon           = 0x0828;
 | 
			
		||||
    const wchar_t TextSearchCloseIcon      = 0x0829;
 | 
			
		||||
 | 
			
		||||
//    void MyFunction(const char* name, const MyMatrix44& v);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -391,7 +391,7 @@ IM_MSVC_RUNTIME_CHECKS_OFF
 | 
			
		|||
static inline float  ImPow(float x, float y)    { return powf(x, y); }          // DragBehaviorT/SliderBehaviorT uses ImPow with either float/double and need the precision
 | 
			
		||||
static inline double ImPow(double x, double y)  { return pow(x, y); }
 | 
			
		||||
static inline float  ImLog(float x)             { return logf(x); }             // DragBehaviorT/SliderBehaviorT uses ImLog with either float/double and need the precision
 | 
			
		||||
static inline double ImLog(double x)            { return log(x); }
 | 
			
		||||
static inline double ImLog(double x)            { return logf(x); }
 | 
			
		||||
static inline int    ImAbs(int x)               { return x < 0 ? -x : x; }
 | 
			
		||||
static inline float  ImAbs(float x)             { return fabsf(x); }
 | 
			
		||||
static inline double ImAbs(double x)            { return fabs(x); }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2126,7 +2126,7 @@ bool ImGui::BBLBeginCombo(const char *label, const char *preview_value, ImGuiCom
 | 
			
		|||
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
 | 
			
		||||
    const float  expected_w = CalcItemWidth();
 | 
			
		||||
    const float  w  = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : expected_w;
 | 
			
		||||
    const ImRect frame_bb(window->DC.CursorPos - ImVec2(0.0, style.FramePadding.y), window->DC.CursorPos + ImVec2(w - arrow_size * 2, label_size.y + style.FramePadding.y));
 | 
			
		||||
    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w - arrow_size * 2, label_size.y + style.FramePadding.y * 2));
 | 
			
		||||
    const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
 | 
			
		||||
    ItemSize(total_bb, style.FramePadding.y);
 | 
			
		||||
    if (!ItemAdd(total_bb, id, &frame_bb)) return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -4175,7 +4175,7 @@ bool ImGui::BBLInputScalar(const char *label, ImGuiDataType data_type, void *p_d
 | 
			
		|||
 | 
			
		||||
    if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt;
 | 
			
		||||
 | 
			
		||||
    char buf[8];
 | 
			
		||||
    char buf[64];
 | 
			
		||||
    DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
 | 
			
		||||
 | 
			
		||||
    bool value_changed = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -7185,7 +7185,9 @@ bool ImGui::BBLImageSelectable(ImTextureID user_texture_id, const ImVec2& size_a
 | 
			
		|||
    // Render
 | 
			
		||||
    if (held && (flags & ImGuiSelectableFlags_DrawHoveredWhenHeld)) hovered = true;
 | 
			
		||||
    if (hovered || selected) {
 | 
			
		||||
        const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
 | 
			
		||||
        ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
 | 
			
		||||
        if (hovered && selected)
 | 
			
		||||
            col = GetColorU32(ImGuiCol_Header);
 | 
			
		||||
        if (arrow_size == 0) {
 | 
			
		||||
            RenderFrame(bb.Min, ImVec2(bb.Max.x - style.WindowPadding.x, bb.Max.y), col, false, 0.0f);
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -7204,9 +7206,9 @@ bool ImGui::BBLImageSelectable(ImTextureID user_texture_id, const ImVec2& size_a
 | 
			
		|||
 | 
			
		||||
    // Render
 | 
			
		||||
    const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
 | 
			
		||||
    ImVec2 p_min = bb.Min + ImVec2(2 * style.ItemSpacing.x, (bb.Max.y - bb.Min.y - font_size.y) / 2);
 | 
			
		||||
    ImVec2 p_min = bb.Min + ImVec2(style.ItemInnerSpacing.x, (bb.Max.y - bb.Min.y - font_size.y) / 2);
 | 
			
		||||
    ImVec2 p_max = p_min + font_size;
 | 
			
		||||
    window->DrawList->AddImage(user_texture_id, p_min, p_max, uv0, uv1, GetColorU32(tint_col));
 | 
			
		||||
    window->DrawList->AddImage(user_texture_id, p_min, p_max, uv0, uv1, selected || (held && hovered) ? GetColorU32(ImVec4(1.f, 1.f, 1.f, 1.f)) : GetColorU32(tint_col));
 | 
			
		||||
 | 
			
		||||
    if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -75,7 +75,7 @@ class _Item {
 | 
			
		|||
 | 
			
		||||
public:
 | 
			
		||||
    int itemid_{ 0 };
 | 
			
		||||
    int extrude_id{ 1 };
 | 
			
		||||
    std::vector<int> extrude_ids;
 | 
			
		||||
    double height{ 0 };
 | 
			
		||||
    double print_temp{ 0 };
 | 
			
		||||
    double bed_temp{ 0 };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -114,13 +114,14 @@ public:
 | 
			
		|||
            double score = LARGE_COST_TO_REJECT+1, best_score = LARGE_COST_TO_REJECT+1;
 | 
			
		||||
            double score_all_plates = 0, score_all_plates_best = std::numeric_limits<double>::max();
 | 
			
		||||
            typename Placer::PackResult result, result_best, result_firstfit;
 | 
			
		||||
            size_t j = 0;
 | 
			
		||||
            int j = 0;
 | 
			
		||||
            while(!was_packed && !cancelled()) {
 | 
			
		||||
                for(; j < placers.size() && !was_packed && !cancelled(); j++) {
 | 
			
		||||
                    result = placers[j].pack(*it, rem(it, store_));
 | 
			
		||||
                    score = result.score();
 | 
			
		||||
                    score_all_plates = std::accumulate(placers.begin(), placers.begin() + j, score,
 | 
			
		||||
                        [](double sum, const Placer& elem) { return sum + elem.score(); });
 | 
			
		||||
                    if (this->unfitindicator_) this->unfitindicator_(it->get().name + " bed_id="+std::to_string(j) + ",score=" + std::to_string(score));
 | 
			
		||||
 | 
			
		||||
                    if(score >= 0 && score < LARGE_COST_TO_REJECT) {
 | 
			
		||||
                        if (bed_id_firstfit == -1) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -257,6 +257,10 @@ void AppConfig::set_defaults()
 | 
			
		|||
        set("mouse_supported", "mouse left/mouse middle/mouse right");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (get("privacy_version").empty()) {
 | 
			
		||||
        set("privacy_version", "00.00.00.00");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (get("rotate_view").empty()) {
 | 
			
		||||
        set("rotate_view", "none/mouse left");
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -462,8 +466,11 @@ std::string AppConfig::load()
 | 
			
		|||
                        for(auto& element: iter.value()) {
 | 
			
		||||
                            if (idx == 0)
 | 
			
		||||
                                m_storage[it.key()]["filament"] = element;
 | 
			
		||||
                            else
 | 
			
		||||
                                m_storage[it.key()]["filament_" + std::to_string(idx)] = element;
 | 
			
		||||
                            else {
 | 
			
		||||
                                auto n = std::to_string(idx);
 | 
			
		||||
                                if (n.length() == 1) n = "0" + n;
 | 
			
		||||
                                m_storage[it.key()]["filament_" + n] = element;
 | 
			
		||||
                            }
 | 
			
		||||
                            idx++;
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -483,8 +490,6 @@ std::string AppConfig::load()
 | 
			
		|||
                            m_filament_presets = iter.value().get<std::vector<std::string>>();
 | 
			
		||||
                        } else if (iter.key() == "filament_colors") {
 | 
			
		||||
                            m_filament_colors = iter.value().get<std::vector<std::string>>();
 | 
			
		||||
                        } else if (iter.key() == "flushing_volumes") {
 | 
			
		||||
                            m_flush_volumes_matrix = iter.value().get<std::vector<float>>();
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            if (iter.value().is_string())
 | 
			
		||||
| 
						 | 
				
			
			@ -577,10 +582,6 @@ void AppConfig::save()
 | 
			
		|||
        j["app"]["filament_colors"].push_back(filament_color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (double flushing_volume : m_flush_volumes_matrix) {
 | 
			
		||||
        j["app"]["flushing_volumes"].push_back(flushing_volume);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Write the other categories.
 | 
			
		||||
    for (const auto& category : m_storage) {
 | 
			
		||||
        if (category.first.empty())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -177,11 +177,6 @@ public:
 | 
			
		|||
        m_filament_colors = filament_colors;
 | 
			
		||||
        m_dirty                = true;
 | 
			
		||||
    }
 | 
			
		||||
    const std::vector<float> &get_flush_volumes_matrix() const { return m_flush_volumes_matrix; }
 | 
			
		||||
    void set_flush_volumes_matrix(const std::vector<float> &flush_volumes_matrix){
 | 
			
		||||
        m_flush_volumes_matrix = flush_volumes_matrix;
 | 
			
		||||
        m_dirty                = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	// return recent/last_opened_folder or recent/settings_folder or empty string.
 | 
			
		||||
	std::string 		get_last_dir() const;
 | 
			
		||||
| 
						 | 
				
			
			@ -281,7 +276,6 @@ private:
 | 
			
		|||
 | 
			
		||||
	std::vector<std::string>									m_filament_presets;
 | 
			
		||||
    std::vector<std::string>									m_filament_colors;
 | 
			
		||||
    std::vector<float>											m_flush_volumes_matrix;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -452,20 +452,25 @@ protected:
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        std::set<int> extruder_ids;
 | 
			
		||||
        int           non_virt_cnt = 0;
 | 
			
		||||
        for (int i = 0; i < m_items.size(); i++) {
 | 
			
		||||
            Item& p = m_items[i];
 | 
			
		||||
            if (p.is_virt_object) continue;
 | 
			
		||||
            extruder_ids.insert(p.extrude_id);
 | 
			
		||||
            // add a large cost if not multi materials on same plate is not allowed 
 | 
			
		||||
            if (!params.allow_multi_materials_on_same_plate)
 | 
			
		||||
                score += LARGE_COST_TO_REJECT * (item.extrude_id != p.extrude_id);
 | 
			
		||||
            extruder_ids.insert(p.extrude_ids.begin(),p.extrude_ids.end());
 | 
			
		||||
            non_virt_cnt++;
 | 
			
		||||
        }
 | 
			
		||||
        extruder_ids.insert(item.extrude_ids.begin(),item.extrude_ids.end());
 | 
			
		||||
 | 
			
		||||
        // add a large cost if not multi materials on same plate is not allowed
 | 
			
		||||
        if (!params.allow_multi_materials_on_same_plate) {
 | 
			
		||||
            // non_virt_cnt==0 means it's the first object, which can be multi-color
 | 
			
		||||
            if (extruder_ids.size() > 1 && non_virt_cnt > 0)
 | 
			
		||||
                score += LARGE_COST_TO_REJECT * 1.1;
 | 
			
		||||
        }
 | 
			
		||||
        // for layered printing, we want extruder change as few as possible
 | 
			
		||||
        // this has very weak effect, CAN NOT use a large weight
 | 
			
		||||
        if (!params.is_seq_print) {
 | 
			
		||||
            extruder_ids.insert(item.extrude_id);
 | 
			
		||||
            score += 1 * std::max(0, ((int)extruder_ids.size() - 1));
 | 
			
		||||
            score += 1 * std::max(0, ((int) extruder_ids.size() - 1));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return std::make_tuple(score, fullbb);
 | 
			
		||||
| 
						 | 
				
			
			@ -601,7 +606,7 @@ public:
 | 
			
		|||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                return i1.bed_temp != i2.bed_temp ? (i1.bed_temp > i2.bed_temp) :
 | 
			
		||||
                    (i1.extrude_id != i2.extrude_id ? (i1.extrude_id < i2.extrude_id) : (i1.area() > i2.area()));
 | 
			
		||||
                    (i1.extrude_ids != i2.extrude_ids ? (i1.extrude_ids.front() < i2.extrude_ids.front()) : (i1.area() > i2.area()));
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
| 
						 | 
				
			
			@ -850,7 +855,7 @@ static void process_arrangeable(const ArrangePolygon &arrpoly,
 | 
			
		|||
    item.binId(arrpoly.bed_idx);
 | 
			
		||||
    item.priority(arrpoly.priority);
 | 
			
		||||
    item.itemId(arrpoly.itemid);
 | 
			
		||||
    item.extrude_id = arrpoly.extrude_ids.front();
 | 
			
		||||
    item.extrude_ids = arrpoly.extrude_ids;
 | 
			
		||||
    item.height = arrpoly.height;
 | 
			
		||||
    item.name = arrpoly.name;
 | 
			
		||||
    //BBS: add virtual object logic
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -165,17 +165,6 @@ static Polygons top_level_outer_brim_islands(const ConstPrintObjectPtrs &top_lev
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // BBS
 | 
			
		||||
        if (!object->tree_support_layers().empty()) {
 | 
			
		||||
            for (const ExPolygon& ex_poly : object->tree_support_layers().front()->lslices) {
 | 
			
		||||
                Polygons contour_offset = offset(ex_poly.contour, brim_object_gap, ClipperLib::jtSquare);
 | 
			
		||||
                for (Polygon& poly : contour_offset)
 | 
			
		||||
                    poly.douglas_peucker(scaled_resolution);
 | 
			
		||||
 | 
			
		||||
                polygons_append(islands_object, std::move(contour_offset));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (const PrintInstance &instance : object->instances())
 | 
			
		||||
            append_and_translate(islands, islands_object, instance);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -248,19 +237,6 @@ static ExPolygons top_level_outer_brim_area(const Print                   &print
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // BBS
 | 
			
		||||
        if (!object->tree_support_layers().empty()) {
 | 
			
		||||
            for (const ExPolygon& ex_poly : object->tree_support_layers().front()->lslices) {
 | 
			
		||||
                if ((brim_type == BrimType::btOuterOnly || brim_type == BrimType::btOuterAndInner || brim_type == BrimType::btAutoBrim) && is_top_outer_brim)
 | 
			
		||||
                    append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_object_gap, jtRound, scaled_resolution), offset(ex_poly.contour, brim_object_gap)));
 | 
			
		||||
 | 
			
		||||
                if (brim_type != BrimType::btNoBrim)
 | 
			
		||||
                    append(no_brim_area_object, offset_ex(ExPolygon(ex_poly.contour), brim_object_gap));
 | 
			
		||||
 | 
			
		||||
                no_brim_area_object.emplace_back(ex_poly.contour);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (const PrintInstance &instance : object->instances()) {
 | 
			
		||||
            append_and_translate(brim_area, brim_area_object, instance);
 | 
			
		||||
            append_and_translate(no_brim_area, no_brim_area_object, instance);
 | 
			
		||||
| 
						 | 
				
			
			@ -360,21 +336,6 @@ static ExPolygons top_level_outer_brim_area(const Print& print, const ConstPrint
 | 
			
		|||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // BBS
 | 
			
		||||
                if (!object->tree_support_layers().empty()) {
 | 
			
		||||
                    for (const ExPolygon& ex_poly : object->tree_support_layers().front()->lslices) {
 | 
			
		||||
                        if ((brim_type == BrimType::btOuterOnly || brim_type == BrimType::btOuterAndInner || brim_type == BrimType::btAutoBrim) && is_top_outer_brim)
 | 
			
		||||
                            append(brim_area_support, diff_ex(offset(ex_poly.contour, brim_width, jtRound, SCALED_RESOLUTION), offset(ex_poly.contour, 0)));
 | 
			
		||||
 | 
			
		||||
                        if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim)
 | 
			
		||||
                            append(no_brim_area_support, offset_ex(ex_poly.holes, -no_brim_offset));
 | 
			
		||||
 | 
			
		||||
                        if (brim_type != BrimType::btNoBrim)
 | 
			
		||||
                            append(no_brim_area_support, offset_ex(ex_poly.contour, 0));
 | 
			
		||||
 | 
			
		||||
                        no_brim_area_support.emplace_back(ex_poly.contour);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                brimToWrite.at(object->id()).sup = false;
 | 
			
		||||
                for (const PrintInstance& instance : object->instances()) {
 | 
			
		||||
                    if (!brim_area_support.empty())
 | 
			
		||||
| 
						 | 
				
			
			@ -543,26 +504,6 @@ static ExPolygons inner_brim_area(const Print& print, const ConstPrintObjectPtrs
 | 
			
		|||
                        no_brim_area_support.emplace_back(support_contour);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // BBS
 | 
			
		||||
                if (!object->tree_support_layers().empty()) {
 | 
			
		||||
                    for (const ExPolygon& ex_poly : object->tree_support_layers().front()->lslices) {
 | 
			
		||||
                        if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btOuterAndInner || brim_type == BrimType::btAutoBrim) {
 | 
			
		||||
                            if (!top_outer_brim)
 | 
			
		||||
                                append(brim_area_support, diff_ex(offset_ex(ex_poly.contour, brim_width + brim_offset, jtRound, SCALED_RESOLUTION), offset_ex(ex_poly.contour, brim_offset)));
 | 
			
		||||
                        }
 | 
			
		||||
                        if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btOuterAndInner)
 | 
			
		||||
                            append(brim_area_support, diff_ex(offset_ex(ex_poly.holes, -brim_offset), offset_ex(ex_poly.holes, -brim_width - brim_offset)));
 | 
			
		||||
                        if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim)
 | 
			
		||||
                            append(no_brim_area_support, diff_ex(offset(ex_poly.contour, no_brim_offset), ex_poly.holes));
 | 
			
		||||
                        if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim)
 | 
			
		||||
                            append(no_brim_area_support, offset_ex(ex_poly.holes, -no_brim_offset));
 | 
			
		||||
                        append(holes_support, ex_poly.holes);
 | 
			
		||||
                        if (brim_type != BrimType::btNoBrim)
 | 
			
		||||
                            append(no_brim_area_support, offset_ex(ex_poly.contour, 0));
 | 
			
		||||
                        no_brim_area_support.emplace_back(ex_poly.contour);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            brimToWrite.at(object->id()).sup = false;
 | 
			
		||||
            for (const PrintInstance& instance : object->instances()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -927,7 +868,6 @@ static ExPolygons outer_inner_brim_area(const Print& print,
 | 
			
		|||
                        double brimWidthRaw = configBrimWidthByVolumeGroups(adhension, maxSpeed, groupVolumePtrs, volumeGroup.slices, groupHeight);
 | 
			
		||||
                        brim_width = scale_(floor(brimWidthRaw / flowWidth / 2) * flowWidth * 2);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    for (const ExPolygon& ex_poly : volumeGroup.slices) {
 | 
			
		||||
                        // BBS: additional brim width will be added if part's adhension area is too small and brim is not generated
 | 
			
		||||
                        float brim_width_mod;
 | 
			
		||||
| 
						 | 
				
			
			@ -986,7 +926,7 @@ static ExPolygons outer_inner_brim_area(const Print& print,
 | 
			
		|||
                    support_material_extruder = printExtruders.front() + 1;
 | 
			
		||||
            }
 | 
			
		||||
            if (support_material_extruder == extruderNo && brimToWrite.at(object->id()).sup) {
 | 
			
		||||
                if (!object->support_layers().empty()) {
 | 
			
		||||
                if (!object->support_layers().empty() && object->support_layers().front()->support_type==stInnerNormal) {
 | 
			
		||||
                    for (const Polygon& support_contour : object->support_layers().front()->support_fills.polygons_covered_by_spacing()) {
 | 
			
		||||
                        // Brim will not be generated for supports
 | 
			
		||||
                        /*
 | 
			
		||||
| 
						 | 
				
			
			@ -1000,8 +940,8 @@ static ExPolygons outer_inner_brim_area(const Print& print,
 | 
			
		|||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                // BBS
 | 
			
		||||
                if (!object->tree_support_layers().empty()) {
 | 
			
		||||
                    for (const ExPolygon& ex_poly : object->tree_support_layers().front()->lslices) {
 | 
			
		||||
                if (!object->support_layers().empty() && object->support_layers().front()->support_type == stInnerTree) {
 | 
			
		||||
                    for (const ExPolygon &ex_poly : object->support_layers().front()->lslices) {
 | 
			
		||||
                        // BBS: additional brim width will be added if adhension area is too small without brim
 | 
			
		||||
                        float brim_width_mod = ex_poly.area() / ex_poly.contour.length() < scaled_half_min_adh_length
 | 
			
		||||
                            && brim_width < scaled_flow_width ? brim_width + scaled_additional_brim_width : brim_width;
 | 
			
		||||
| 
						 | 
				
			
			@ -1036,11 +976,16 @@ static ExPolygons outer_inner_brim_area(const Print& print,
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (!bedExPoly.empty())
 | 
			
		||||
    if (!bedExPoly.empty()){
 | 
			
		||||
        auto plateOffset = print.get_plate_origin();
 | 
			
		||||
        bedExPoly.front().translate(scale_(plateOffset.x()), scale_(plateOffset.y()));
 | 
			
		||||
        no_brim_area.push_back(bedExPoly.front());
 | 
			
		||||
    for (const PrintObject* object : print.objects())
 | 
			
		||||
        if (brimAreaMap.find(object->id()) != brimAreaMap.end()) {
 | 
			
		||||
    }
 | 
			
		||||
    for (const PrintObject* object : print.objects()) {
 | 
			
		||||
        if (brimAreaMap.find(object->id()) != brimAreaMap.end())
 | 
			
		||||
        {
 | 
			
		||||
            brimAreaMap[object->id()] = diff_ex(brimAreaMap[object->id()], no_brim_area);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (supportBrimAreaMap.find(object->id()) != supportBrimAreaMap.end())
 | 
			
		||||
            supportBrimAreaMap[object->id()] = diff_ex(supportBrimAreaMap[object->id()], no_brim_area);
 | 
			
		||||
| 
						 | 
				
			
			@ -1220,13 +1165,6 @@ static void make_inner_island_brim(const Print& print, const ConstPrintObjectPtr
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!object->tree_support_layers().empty()) {
 | 
			
		||||
            for (const ExPolygon& ex_poly : object->tree_support_layers().front()->lslices) {
 | 
			
		||||
                Polygon counter = ex_poly.contour;
 | 
			
		||||
                save_polygon_if_is_inner_island(holes_area, counter, hole_island_pair);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //BBS: 3 generate loops, only save part of loop which inside hole
 | 
			
		||||
        const float    brim_offset = scale_(object->config().brim_object_gap.value);
 | 
			
		||||
        const float    brim_width = scale_(object->config().brim_width.value);
 | 
			
		||||
| 
						 | 
				
			
			@ -1369,13 +1307,6 @@ static void make_inner_island_brim(const Print& print, const ConstPrintObjectPtr
 | 
			
		|||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!object->tree_support_layers().empty()) {
 | 
			
		||||
                    for (const ExPolygon& ex_poly : object->tree_support_layers().front()->lslices) {
 | 
			
		||||
                        Polygon counter = ex_poly.contour;
 | 
			
		||||
                        save_polygon_if_is_inner_island(holes_area, counter, hole_island_pair_supports);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                //BBS: 3 generate loops, only save part of loop which inside hole
 | 
			
		||||
                const float    brim_offset = scale_(object->config().brim_object_gap.value);
 | 
			
		||||
                const float    brim_width = floor(scale_(object->config().brim_width.value) / 2 / flow.scaled_spacing()) * 2 * flow.scaled_spacing();
 | 
			
		||||
| 
						 | 
				
			
			@ -1587,10 +1518,10 @@ Polygons tryExPolygonOffset(const ExPolygons islandAreaEx, const Print& print)
 | 
			
		|||
        for (ExPolygon& poly_ex : islands_ex)
 | 
			
		||||
            poly_ex.douglas_peucker(resolution);
 | 
			
		||||
        polygons_append(loops, to_polygons(islands_ex));
 | 
			
		||||
        islands_ex = offset_ex(std::move(islands_ex), -1.4f*float(flow.scaled_spacing()), jtRound, resolution);
 | 
			
		||||
        islands_ex = offset_ex(std::move(islands_ex), -1.3f*float(flow.scaled_spacing()), jtRound, resolution);
 | 
			
		||||
        for (ExPolygon& poly_ex : islands_ex)
 | 
			
		||||
            poly_ex.douglas_peucker(resolution);
 | 
			
		||||
        islands_ex = offset_ex(std::move(islands_ex), 0.4f*float(flow.scaled_spacing()), jtRound, resolution);
 | 
			
		||||
        islands_ex = offset_ex(std::move(islands_ex), 0.3f*float(flow.scaled_spacing()), jtRound, resolution);
 | 
			
		||||
    }
 | 
			
		||||
    return loops;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1663,13 +1594,6 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
 | 
			
		|||
                ex_poly_translated.translate(instance.shift.x(), instance.shift.y());
 | 
			
		||||
                bbx.merge(get_extents(ex_poly_translated));
 | 
			
		||||
            }
 | 
			
		||||
        if (!object->tree_support_layers().empty())
 | 
			
		||||
        for (const Polygon& ex_poly : object->tree_support_layers().front()->support_fills.polygons_covered_by_spacing())
 | 
			
		||||
            for (const PrintInstance& instance : object->instances()) {
 | 
			
		||||
                auto ex_poly_translated = ex_poly;
 | 
			
		||||
                ex_poly_translated.translate(instance.shift.x(), instance.shift.y());
 | 
			
		||||
                bbx.merge(get_extents(ex_poly_translated));
 | 
			
		||||
            }
 | 
			
		||||
        if (supportBrimAreaMap.find(printObjID) != supportBrimAreaMap.end()) {
 | 
			
		||||
            for (const ExPolygon& ex_poly : supportBrimAreaMap.at(printObjID))
 | 
			
		||||
                bbx.merge(get_extents(ex_poly.contour));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,9 @@ project(libslic3r)
 | 
			
		|||
 | 
			
		||||
include(PrecompiledHeader)
 | 
			
		||||
 | 
			
		||||
string(TIMESTAMP COMPILE_TIME %Y%m%d-%H%M%S)
 | 
			
		||||
set(SLIC3R_BUILD_TIME ${COMPILE_TIME})
 | 
			
		||||
 | 
			
		||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libslic3r_version.h.in ${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h @ONLY)
 | 
			
		||||
 | 
			
		||||
if (MINGW)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -298,9 +298,16 @@ ConfigOption* ConfigOptionDef::create_default_option() const
 | 
			
		|||
            return new ConfigOptionEnumGeneric(this->enum_keys_map, this->default_value->getInt());
 | 
			
		||||
 | 
			
		||||
        if (type == coEnums) {
 | 
			
		||||
            ConfigOptionEnumsGeneric* opt = dynamic_cast<ConfigOptionEnumsGeneric*>(this->default_value->clone());
 | 
			
		||||
            opt->keys_map = this->enum_keys_map;
 | 
			
		||||
            return opt;
 | 
			
		||||
            auto dft = this->default_value->clone();
 | 
			
		||||
            if (dft->nullable()) {
 | 
			
		||||
                ConfigOptionEnumsGenericNullable *opt = dynamic_cast<ConfigOptionEnumsGenericNullable *>(this->default_value->clone());
 | 
			
		||||
                opt->keys_map = this->enum_keys_map;
 | 
			
		||||
                return opt;
 | 
			
		||||
            } else {
 | 
			
		||||
                ConfigOptionEnumsGeneric *opt = dynamic_cast<ConfigOptionEnumsGeneric *>(this->default_value->clone());
 | 
			
		||||
                opt->keys_map = this->enum_keys_map;
 | 
			
		||||
                return opt;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this->default_value->clone();
 | 
			
		||||
| 
						 | 
				
			
			@ -743,6 +750,9 @@ ConfigSubstitutions ConfigBase::load_from_json(const std::string &file, ForwardC
 | 
			
		|||
int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContext& substitution_context, bool load_inherits_to_config, std::map<std::string, std::string>& key_values, std::string& reason)
 | 
			
		||||
{
 | 
			
		||||
    json j;
 | 
			
		||||
    std::list<std::string> different_settings_append;
 | 
			
		||||
    std::string new_support_style;
 | 
			
		||||
    bool is_project_settings = false;
 | 
			
		||||
    try {
 | 
			
		||||
        boost::nowide::ifstream ifs(file);
 | 
			
		||||
        ifs >> j;
 | 
			
		||||
| 
						 | 
				
			
			@ -762,6 +772,8 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex
 | 
			
		|||
            }
 | 
			
		||||
            else if (boost::iequals(it.key(), BBL_JSON_KEY_NAME)) {
 | 
			
		||||
                key_values.emplace(BBL_JSON_KEY_NAME, it.value());
 | 
			
		||||
                if (it.value() == "project_settings")
 | 
			
		||||
                    is_project_settings = true;
 | 
			
		||||
            }
 | 
			
		||||
            else if (boost::iequals(it.key(), BBL_JSON_KEY_URL)) {
 | 
			
		||||
                key_values.emplace(BBL_JSON_KEY_URL, it.value());
 | 
			
		||||
| 
						 | 
				
			
			@ -791,6 +803,15 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex
 | 
			
		|||
                if (it.value().is_string()) {
 | 
			
		||||
                    //bool test1 = (it.key() == std::string("end_gcode"));
 | 
			
		||||
                    this->set_deserialize(opt_key, it.value(), substitution_context);
 | 
			
		||||
                    //some logic for special values
 | 
			
		||||
                    if (opt_key == "support_type") {
 | 
			
		||||
                        //std::string new_value = dynamic_cast<ConfigOptionString*>(this->option(opt_key))->value;
 | 
			
		||||
                        if (it.value() == "hybrid(auto)") {
 | 
			
		||||
                            different_settings_append.push_back(opt_key);
 | 
			
		||||
                            different_settings_append.push_back("support_style");
 | 
			
		||||
                            new_support_style = "tree_hybrid";
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else if (it.value().is_array()) {
 | 
			
		||||
                    t_config_option_key opt_key_src = opt_key;
 | 
			
		||||
| 
						 | 
				
			
			@ -856,6 +877,62 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex
 | 
			
		|||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (!different_settings_append.empty()) {
 | 
			
		||||
            if (!new_support_style.empty()) {
 | 
			
		||||
                ConfigOptionEnum<SupportMaterialStyle>* opt = this->option<ConfigOptionEnum<SupportMaterialStyle>>("support_style", true);
 | 
			
		||||
                opt->value = smsTreeHybrid;
 | 
			
		||||
            }
 | 
			
		||||
            if (is_project_settings) {
 | 
			
		||||
                std::vector<std::string>& different_settings = this->option<ConfigOptionStrings>("different_settings_to_system", true)->values;
 | 
			
		||||
                size_t size = different_settings.size();
 | 
			
		||||
                if (size == 0) {
 | 
			
		||||
                    size = this->option<ConfigOptionStrings>("filament_settings_id")->values.size() + 2;
 | 
			
		||||
                    different_settings.resize(size);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                std::vector<bool> is_first(size, false);
 | 
			
		||||
                std::vector<std::vector<std::string>> original_diffs(size);
 | 
			
		||||
                for (int index = 0; index < size; index++)
 | 
			
		||||
                {
 | 
			
		||||
                    if (different_settings[index].empty()) {
 | 
			
		||||
                        is_first[index] = true;
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        Slic3r::unescape_strings_cstyle(different_settings[index], original_diffs[index]);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                for (auto diff_key : different_settings_append)
 | 
			
		||||
                {
 | 
			
		||||
                    //get the index in the group
 | 
			
		||||
                    int index = 0;
 | 
			
		||||
                    bool need_insert = true;
 | 
			
		||||
                    if (diff_key == "support_type")
 | 
			
		||||
                        index = 0;
 | 
			
		||||
                    else if (diff_key == "support_style")
 | 
			
		||||
                        index = 0;
 | 
			
		||||
 | 
			
		||||
                    //check whether exist firstly
 | 
			
		||||
                    if (!original_diffs[index].empty()) {
 | 
			
		||||
                        for (int j = 0; j < original_diffs[index].size(); j++) {
 | 
			
		||||
                            if (original_diffs[index][j] == diff_key) {
 | 
			
		||||
                                need_insert = false;
 | 
			
		||||
                                break;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (!need_insert)
 | 
			
		||||
                        continue;
 | 
			
		||||
 | 
			
		||||
                    //insert this key
 | 
			
		||||
                    if (!is_first[index])
 | 
			
		||||
                        different_settings[index] += ";";
 | 
			
		||||
                    else
 | 
			
		||||
                        is_first[index] = false;
 | 
			
		||||
                    different_settings[index] += diff_key;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    catch (const std::ifstream::failure &err)  {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -771,6 +771,8 @@ public:
 | 
			
		|||
    ConfigOptionIntsTempl() : ConfigOptionVector<int>() {}
 | 
			
		||||
    explicit ConfigOptionIntsTempl(size_t n, int value) : ConfigOptionVector<int>(n, value) {}
 | 
			
		||||
    explicit ConfigOptionIntsTempl(std::initializer_list<int> il) : ConfigOptionVector<int>(std::move(il)) {}
 | 
			
		||||
    explicit ConfigOptionIntsTempl(const std::vector<int> &vec) : ConfigOptionVector<int>(vec) {}
 | 
			
		||||
    explicit ConfigOptionIntsTempl(std::vector<int> &&vec) : ConfigOptionVector<int>(std::move(vec)) {}
 | 
			
		||||
 | 
			
		||||
    static ConfigOptionType static_type() { return coInts; }
 | 
			
		||||
    ConfigOptionType        type()  const override { return static_type(); }
 | 
			
		||||
| 
						 | 
				
			
			@ -1640,33 +1642,37 @@ private:
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
// BBS
 | 
			
		||||
class ConfigOptionEnumsGeneric : public ConfigOptionInts
 | 
			
		||||
template <bool NULLABLE>
 | 
			
		||||
class ConfigOptionEnumsGenericTempl : public ConfigOptionInts
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    ConfigOptionEnumsGeneric(const t_config_enum_values* keys_map = nullptr) : keys_map(keys_map) {}
 | 
			
		||||
    explicit ConfigOptionEnumsGeneric(const t_config_enum_values* keys_map, size_t size, int value) : ConfigOptionInts(size, value), keys_map(keys_map) {}
 | 
			
		||||
    explicit ConfigOptionEnumsGeneric(std::initializer_list<int> il) : ConfigOptionInts(std::move(il)), keys_map(keys_map) {}
 | 
			
		||||
    ConfigOptionEnumsGenericTempl(const t_config_enum_values *keys_map = nullptr) : keys_map(keys_map) {}
 | 
			
		||||
    explicit ConfigOptionEnumsGenericTempl(const t_config_enum_values *keys_map, size_t size, int value) : ConfigOptionInts(size, value), keys_map(keys_map) {}
 | 
			
		||||
    explicit ConfigOptionEnumsGenericTempl(std::initializer_list<int> il) : ConfigOptionInts(std::move(il)), keys_map(keys_map) {}
 | 
			
		||||
    explicit ConfigOptionEnumsGenericTempl(const std::vector<int> &vec) : ConfigOptionInts(vec) {}
 | 
			
		||||
    explicit ConfigOptionEnumsGenericTempl(std::vector<int> &&vec) : ConfigOptionInts(std::move(vec)) {}
 | 
			
		||||
 | 
			
		||||
    const t_config_enum_values* keys_map = nullptr;
 | 
			
		||||
 | 
			
		||||
    static ConfigOptionType     static_type() { return coEnums; }
 | 
			
		||||
    ConfigOptionType            type()  const override { return static_type(); }
 | 
			
		||||
    ConfigOption* clone() const override { return new ConfigOptionEnumsGeneric(*this); }
 | 
			
		||||
    ConfigOptionEnumsGeneric& operator= (const ConfigOption* opt) { this->set(opt); return *this; }
 | 
			
		||||
    bool                        operator< (const ConfigOptionIntsTempl& rhs) const throw() { return this->values < rhs.values; }
 | 
			
		||||
    ConfigOption* clone() const override { return new ConfigOptionEnumsGenericTempl(*this); }
 | 
			
		||||
    ConfigOptionEnumsGenericTempl& operator= (const ConfigOption* opt) { this->set(opt); return *this; }
 | 
			
		||||
    bool                        operator< (const ConfigOptionInts& rhs) const throw() { return this->values < rhs.values; }
 | 
			
		||||
 | 
			
		||||
    bool                        operator==(const ConfigOptionIntsTempl& rhs) const throw()
 | 
			
		||||
    bool                        operator==(const ConfigOptionInts& rhs) const throw()
 | 
			
		||||
    {
 | 
			
		||||
        if (rhs.type() != this->type())
 | 
			
		||||
            throw ConfigurationError("ConfigOptionEnumsGeneric: Comparing incompatible types");
 | 
			
		||||
        return this->values == rhs.values;
 | 
			
		||||
    }
 | 
			
		||||
    bool nullable() const override { return NULLABLE; }
 | 
			
		||||
 | 
			
		||||
    void set(const ConfigOption* rhs) override {
 | 
			
		||||
        if (rhs->type() != this->type())
 | 
			
		||||
            throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
 | 
			
		||||
        // rhs could be of the following type: ConfigOptionEnumsGeneric
 | 
			
		||||
        this->values = dynamic_cast<const ConfigOptionEnumsGeneric*>(rhs)->values;
 | 
			
		||||
        this->values = dynamic_cast<const ConfigOptionEnumsGenericTempl *>(rhs)->values;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string serialize() const override
 | 
			
		||||
| 
						 | 
				
			
			@ -1701,7 +1707,10 @@ public:
 | 
			
		|||
        while (std::getline(is, item_str, ',')) {
 | 
			
		||||
            boost::trim(item_str);
 | 
			
		||||
            if (item_str == "nil") {
 | 
			
		||||
                return false;
 | 
			
		||||
                if (NULLABLE)
 | 
			
		||||
                    this->values.push_back(nil_value());
 | 
			
		||||
                else
 | 
			
		||||
                    throw ConfigurationError("Deserializing nil into a non-nullable object");
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                auto it = this->keys_map->find(item_str);
 | 
			
		||||
| 
						 | 
				
			
			@ -1716,15 +1725,26 @@ public:
 | 
			
		|||
private:
 | 
			
		||||
    void serialize_single_value(std::ostringstream& ss, const int v) const
 | 
			
		||||
    {
 | 
			
		||||
        for (const auto& kvp : *this->keys_map)
 | 
			
		||||
            if (kvp.second == v)
 | 
			
		||||
                ss << kvp.first;
 | 
			
		||||
        if (v == nil_value()) {
 | 
			
		||||
            if (NULLABLE)
 | 
			
		||||
                ss << "nil";
 | 
			
		||||
            else
 | 
			
		||||
                throw ConfigurationError("Serializing NaN");
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            for (const auto& kvp : *this->keys_map)
 | 
			
		||||
                if (kvp.second == v)
 | 
			
		||||
                    ss << kvp.first;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    friend class cereal::access;
 | 
			
		||||
    template<class Archive> void serialize(Archive& ar) { ar(cereal::base_class<ConfigOptionVector<int>>(this)); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using ConfigOptionEnumsGeneric         = ConfigOptionEnumsGenericTempl<false>;
 | 
			
		||||
using ConfigOptionEnumsGenericNullable = ConfigOptionEnumsGenericTempl<true>;
 | 
			
		||||
 | 
			
		||||
// Definition of a configuration value for the purpose of GUI presentation, editing, value mapping and config file handling.
 | 
			
		||||
class ConfigOptionDef
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1367,61 +1367,61 @@ ModelVolumeType type_from_string(const std::string &s)
 | 
			
		|||
 | 
			
		||||
    void _3MF_Importer::_extract_custom_gcode_per_print_z_from_archive(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat)
 | 
			
		||||
    {
 | 
			
		||||
        if (stat.m_uncomp_size > 0) {
 | 
			
		||||
            std::string buffer((size_t)stat.m_uncomp_size, 0);
 | 
			
		||||
            mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
 | 
			
		||||
            if (res == 0) {
 | 
			
		||||
                add_error("Error while reading custom Gcodes per height data to buffer");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        //if (stat.m_uncomp_size > 0) {
 | 
			
		||||
        //    std::string buffer((size_t)stat.m_uncomp_size, 0);
 | 
			
		||||
        //    mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
 | 
			
		||||
        //    if (res == 0) {
 | 
			
		||||
        //        add_error("Error while reading custom Gcodes per height data to buffer");
 | 
			
		||||
        //        return;
 | 
			
		||||
        //    }
 | 
			
		||||
 | 
			
		||||
            std::istringstream iss(buffer); // wrap returned xml to istringstream
 | 
			
		||||
            pt::ptree main_tree;
 | 
			
		||||
            pt::read_xml(iss, main_tree);
 | 
			
		||||
        //    std::istringstream iss(buffer); // wrap returned xml to istringstream
 | 
			
		||||
        //    pt::ptree main_tree;
 | 
			
		||||
        //    pt::read_xml(iss, main_tree);
 | 
			
		||||
 | 
			
		||||
            if (main_tree.front().first != "custom_gcodes_per_print_z")
 | 
			
		||||
                return;
 | 
			
		||||
            pt::ptree code_tree = main_tree.front().second;
 | 
			
		||||
        //    if (main_tree.front().first != "custom_gcodes_per_print_z")
 | 
			
		||||
        //        return;
 | 
			
		||||
        //    pt::ptree code_tree = main_tree.front().second;
 | 
			
		||||
 | 
			
		||||
            m_model->custom_gcode_per_print_z.gcodes.clear();
 | 
			
		||||
        //    m_model->custom_gcode_per_print_z.gcodes.clear();
 | 
			
		||||
 | 
			
		||||
            for (const auto& code : code_tree) {
 | 
			
		||||
                if (code.first == "mode") {
 | 
			
		||||
                    pt::ptree tree = code.second;
 | 
			
		||||
                    std::string mode = tree.get<std::string>("<xmlattr>.value");
 | 
			
		||||
                    m_model->custom_gcode_per_print_z.mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder :
 | 
			
		||||
                                                             mode == CustomGCode::MultiAsSingleMode  ? CustomGCode::Mode::MultiAsSingle  :
 | 
			
		||||
                                                             CustomGCode::Mode::MultiExtruder;
 | 
			
		||||
                }
 | 
			
		||||
                if (code.first != "code")
 | 
			
		||||
                    continue;
 | 
			
		||||
        //    for (const auto& code : code_tree) {
 | 
			
		||||
        //        if (code.first == "mode") {
 | 
			
		||||
        //            pt::ptree tree = code.second;
 | 
			
		||||
        //            std::string mode = tree.get<std::string>("<xmlattr>.value");
 | 
			
		||||
        //            m_model->custom_gcode_per_print_z.mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder :
 | 
			
		||||
        //                                                     mode == CustomGCode::MultiAsSingleMode  ? CustomGCode::Mode::MultiAsSingle  :
 | 
			
		||||
        //                                                     CustomGCode::Mode::MultiExtruder;
 | 
			
		||||
        //        }
 | 
			
		||||
        //        if (code.first != "code")
 | 
			
		||||
        //            continue;
 | 
			
		||||
 | 
			
		||||
                pt::ptree tree = code.second;
 | 
			
		||||
                double print_z          = tree.get<double>      ("<xmlattr>.print_z" );
 | 
			
		||||
                int extruder            = tree.get<int>         ("<xmlattr>.extruder");
 | 
			
		||||
                std::string color       = tree.get<std::string> ("<xmlattr>.color"   );
 | 
			
		||||
        //        pt::ptree tree = code.second;
 | 
			
		||||
        //        double print_z          = tree.get<double>      ("<xmlattr>.print_z" );
 | 
			
		||||
        //        int extruder            = tree.get<int>         ("<xmlattr>.extruder");
 | 
			
		||||
        //        std::string color       = tree.get<std::string> ("<xmlattr>.color"   );
 | 
			
		||||
 | 
			
		||||
                CustomGCode::Type   type;
 | 
			
		||||
                std::string         extra;
 | 
			
		||||
                pt::ptree attr_tree = tree.find("<xmlattr>")->second;
 | 
			
		||||
                if (attr_tree.find("type") == attr_tree.not_found()) {
 | 
			
		||||
                    // It means that data was saved in old version (2.2.0 and older) of PrusaSlicer
 | 
			
		||||
                    // read old data ...
 | 
			
		||||
                    std::string gcode       = tree.get<std::string> ("<xmlattr>.gcode");
 | 
			
		||||
                    // ... and interpret them to the new data
 | 
			
		||||
                    type  = gcode == "M600"           ? CustomGCode::ColorChange :
 | 
			
		||||
                            gcode == "M601"           ? CustomGCode::PausePrint  :
 | 
			
		||||
                            gcode == "tool_change"    ? CustomGCode::ToolChange  :   CustomGCode::Custom;
 | 
			
		||||
                    extra = type == CustomGCode::PausePrint ? color :
 | 
			
		||||
                            type == CustomGCode::Custom     ? gcode : "";
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    type  = static_cast<CustomGCode::Type>(tree.get<int>("<xmlattr>.type"));
 | 
			
		||||
                    extra = tree.get<std::string>("<xmlattr>.extra");
 | 
			
		||||
                }
 | 
			
		||||
                m_model->custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra}) ;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        //        CustomGCode::Type   type;
 | 
			
		||||
        //        std::string         extra;
 | 
			
		||||
        //        pt::ptree attr_tree = tree.find("<xmlattr>")->second;
 | 
			
		||||
        //        if (attr_tree.find("type") == attr_tree.not_found()) {
 | 
			
		||||
        //            // It means that data was saved in old version (2.2.0 and older) of PrusaSlicer
 | 
			
		||||
        //            // read old data ...
 | 
			
		||||
        //            std::string gcode       = tree.get<std::string> ("<xmlattr>.gcode");
 | 
			
		||||
        //            // ... and interpret them to the new data
 | 
			
		||||
        //            type  = gcode == "M600"           ? CustomGCode::ColorChange :
 | 
			
		||||
        //                    gcode == "M601"           ? CustomGCode::PausePrint  :
 | 
			
		||||
        //                    gcode == "tool_change"    ? CustomGCode::ToolChange  :   CustomGCode::Custom;
 | 
			
		||||
        //            extra = type == CustomGCode::PausePrint ? color :
 | 
			
		||||
        //                    type == CustomGCode::Custom     ? gcode : "";
 | 
			
		||||
        //        }
 | 
			
		||||
        //        else {
 | 
			
		||||
        //            type  = static_cast<CustomGCode::Type>(tree.get<int>("<xmlattr>.type"));
 | 
			
		||||
        //            extra = tree.get<std::string>("<xmlattr>.extra");
 | 
			
		||||
        //        }
 | 
			
		||||
        //        m_model->custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra}) ;
 | 
			
		||||
        //    }
 | 
			
		||||
        //}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void _3MF_Importer::_handle_start_model_xml_element(const char* name, const char** attributes)
 | 
			
		||||
| 
						 | 
				
			
			@ -3182,54 +3182,55 @@ ModelVolumeType type_from_string(const std::string &s)
 | 
			
		|||
 | 
			
		||||
bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config)
 | 
			
		||||
{
 | 
			
		||||
    std::string out = "";
 | 
			
		||||
 | 
			
		||||
    if (!model.custom_gcode_per_print_z.gcodes.empty()) {
 | 
			
		||||
        pt::ptree tree;
 | 
			
		||||
        pt::ptree& main_tree = tree.add("custom_gcodes_per_print_z", "");
 | 
			
		||||
 | 
			
		||||
        for (const CustomGCode::Item& code : model.custom_gcode_per_print_z.gcodes) {
 | 
			
		||||
            pt::ptree& code_tree = main_tree.add("code", "");
 | 
			
		||||
 | 
			
		||||
            // store data of custom_gcode_per_print_z
 | 
			
		||||
            code_tree.put("<xmlattr>.print_z"   , code.print_z  );
 | 
			
		||||
            code_tree.put("<xmlattr>.type"      , static_cast<int>(code.type));
 | 
			
		||||
            code_tree.put("<xmlattr>.extruder"  , code.extruder );
 | 
			
		||||
            code_tree.put("<xmlattr>.color"     , code.color    );
 | 
			
		||||
            code_tree.put("<xmlattr>.extra"     , code.extra    );
 | 
			
		||||
 | 
			
		||||
            //BBS
 | 
			
		||||
            std::string gcode = //code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode")    :
 | 
			
		||||
                                code.type == CustomGCode::PausePrint  ? config->opt_string("machine_pause_gcode")     :
 | 
			
		||||
                                code.type == CustomGCode::Template    ? config->opt_string("template_custom_gcode")   :
 | 
			
		||||
                                code.type == CustomGCode::ToolChange  ? "tool_change"   : code.extra;
 | 
			
		||||
            code_tree.put("<xmlattr>.gcode"     , gcode   );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pt::ptree& mode_tree = main_tree.add("mode", "");
 | 
			
		||||
        // store mode of a custom_gcode_per_print_z
 | 
			
		||||
        mode_tree.put("<xmlattr>.value", model.custom_gcode_per_print_z.mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode :
 | 
			
		||||
                                         model.custom_gcode_per_print_z.mode == CustomGCode::Mode::MultiAsSingle ?  CustomGCode::MultiAsSingleMode :
 | 
			
		||||
                                         CustomGCode::MultiExtruderMode);
 | 
			
		||||
 | 
			
		||||
        if (!tree.empty()) {
 | 
			
		||||
            std::ostringstream oss;
 | 
			
		||||
            boost::property_tree::write_xml(oss, tree);
 | 
			
		||||
            out = oss.str();
 | 
			
		||||
 | 
			
		||||
            // Post processing("beautification") of the output string
 | 
			
		||||
            boost::replace_all(out, "><", ">\n<");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!out.empty()) {
 | 
			
		||||
        if (!mz_zip_writer_add_mem(&archive, CUSTOM_GCODE_PER_PRINT_Z_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
 | 
			
		||||
            add_error("Unable to add custom Gcodes per print_z file to archive");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
    //std::string out = "";
 | 
			
		||||
 | 
			
		||||
    //if (!model.custom_gcode_per_print_z.gcodes.empty()) {
 | 
			
		||||
    //    pt::ptree tree;
 | 
			
		||||
    //    pt::ptree& main_tree = tree.add("custom_gcodes_per_print_z", "");
 | 
			
		||||
 | 
			
		||||
    //    for (const CustomGCode::Item& code : model.custom_gcode_per_print_z.gcodes) {
 | 
			
		||||
    //        pt::ptree& code_tree = main_tree.add("code", "");
 | 
			
		||||
 | 
			
		||||
    //        // store data of custom_gcode_per_print_z
 | 
			
		||||
    //        code_tree.put("<xmlattr>.print_z"   , code.print_z  );
 | 
			
		||||
    //        code_tree.put("<xmlattr>.type"      , static_cast<int>(code.type));
 | 
			
		||||
    //        code_tree.put("<xmlattr>.extruder"  , code.extruder );
 | 
			
		||||
    //        code_tree.put("<xmlattr>.color"     , code.color    );
 | 
			
		||||
    //        code_tree.put("<xmlattr>.extra"     , code.extra    );
 | 
			
		||||
 | 
			
		||||
    //        //BBS
 | 
			
		||||
    //        std::string gcode = //code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode")    :
 | 
			
		||||
    //                            code.type == CustomGCode::PausePrint  ? config->opt_string("machine_pause_gcode")     :
 | 
			
		||||
    //                            code.type == CustomGCode::Template    ? config->opt_string("template_custom_gcode")   :
 | 
			
		||||
    //                            code.type == CustomGCode::ToolChange  ? "tool_change"   : code.extra;
 | 
			
		||||
    //        code_tree.put("<xmlattr>.gcode"     , gcode   );
 | 
			
		||||
    //    }
 | 
			
		||||
 | 
			
		||||
    //    pt::ptree& mode_tree = main_tree.add("mode", "");
 | 
			
		||||
    //    // store mode of a custom_gcode_per_print_z
 | 
			
		||||
    //    mode_tree.put("<xmlattr>.value", model.custom_gcode_per_print_z.mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode :
 | 
			
		||||
    //                                     model.custom_gcode_per_print_z.mode == CustomGCode::Mode::MultiAsSingle ?  CustomGCode::MultiAsSingleMode :
 | 
			
		||||
    //                                     CustomGCode::MultiExtruderMode);
 | 
			
		||||
 | 
			
		||||
    //    if (!tree.empty()) {
 | 
			
		||||
    //        std::ostringstream oss;
 | 
			
		||||
    //        boost::property_tree::write_xml(oss, tree);
 | 
			
		||||
    //        out = oss.str();
 | 
			
		||||
 | 
			
		||||
    //        // Post processing("beautification") of the output string
 | 
			
		||||
    //        boost::replace_all(out, "><", ">\n<");
 | 
			
		||||
    //    }
 | 
			
		||||
    //}
 | 
			
		||||
 | 
			
		||||
    //if (!out.empty()) {
 | 
			
		||||
    //    if (!mz_zip_writer_add_mem(&archive, CUSTOM_GCODE_PER_PRINT_Z_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
 | 
			
		||||
    //        add_error("Unable to add custom Gcodes per print_z file to archive");
 | 
			
		||||
    //        return false;
 | 
			
		||||
    //    }
 | 
			
		||||
    //}
 | 
			
		||||
 | 
			
		||||
    //return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Perform conversions based on the config values available.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,8 +28,8 @@
 | 
			
		|||
#include "TopExp_Explorer.hxx"
 | 
			
		||||
#include "BRep_Tool.hxx"
 | 
			
		||||
 | 
			
		||||
const double STEP_TRANS_CHORD_ERROR = 0.005;
 | 
			
		||||
const double STEP_TRANS_ANGLE_RES = 1;
 | 
			
		||||
const double STEP_TRANS_CHORD_ERROR = 0.0025;
 | 
			
		||||
const double STEP_TRANS_ANGLE_RES = 0.5;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
| 
						 | 
				
			
			@ -210,13 +210,15 @@ static void getNamedSolids(const TopLoc_Location& location, const std::string& p
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool load_step(const char *path, Model *model, ImportStepProgressFn stepFn, StepIsUtf8Fn isUtf8Fn)
 | 
			
		||||
bool load_step(const char *path, Model *model, bool& is_cancel, ImportStepProgressFn stepFn, StepIsUtf8Fn isUtf8Fn)
 | 
			
		||||
{
 | 
			
		||||
    bool cb_cancel = false;
 | 
			
		||||
    if (stepFn) {
 | 
			
		||||
        stepFn(LOAD_STEP_STAGE_READ_FILE, 0, 1, cb_cancel);
 | 
			
		||||
        if (cb_cancel)
 | 
			
		||||
        is_cancel = cb_cancel;
 | 
			
		||||
        if (cb_cancel) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!StepPreProcessor::isUtf8File(path) && isUtf8Fn)
 | 
			
		||||
| 
						 | 
				
			
			@ -248,6 +250,7 @@ bool load_step(const char *path, Model *model, ImportStepProgressFn stepFn, Step
 | 
			
		|||
        if (stepFn) {
 | 
			
		||||
            if ((iLabel % stage_unit2) == 0) {
 | 
			
		||||
                stepFn(LOAD_STEP_STAGE_GET_SOLID, iLabel, topShapeLength, cb_cancel);
 | 
			
		||||
                is_cancel = cb_cancel;
 | 
			
		||||
            }
 | 
			
		||||
            if (cb_cancel) {
 | 
			
		||||
                shapeTool.reset(nullptr);
 | 
			
		||||
| 
						 | 
				
			
			@ -345,6 +348,7 @@ bool load_step(const char *path, Model *model, ImportStepProgressFn stepFn, Step
 | 
			
		|||
        if (stepFn) {
 | 
			
		||||
            if ((i % stage_unit3) == 0) {
 | 
			
		||||
                stepFn(LOAD_STEP_STAGE_GET_MESH, i, stl.size(), cb_cancel);
 | 
			
		||||
                is_cancel = cb_cancel;
 | 
			
		||||
            }
 | 
			
		||||
            if (cb_cancel) {
 | 
			
		||||
                model->delete_object(new_object);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,7 @@ typedef std::function<void(int load_stage, int current, int total, bool& cancel)
 | 
			
		|||
typedef std::function<void(bool isUtf8)> StepIsUtf8Fn;
 | 
			
		||||
 | 
			
		||||
//BBS: Load an step file into a provided model.
 | 
			
		||||
extern bool load_step(const char *path, Model *model, ImportStepProgressFn proFn = nullptr, StepIsUtf8Fn isUtf8Fn = nullptr);
 | 
			
		||||
extern bool load_step(const char *path, Model *model, bool& is_cancel, ImportStepProgressFn proFn = nullptr, StepIsUtf8Fn isUtf8Fn = nullptr);
 | 
			
		||||
 | 
			
		||||
//BBS: Used to detect what kind of encoded type is used in name field of step
 | 
			
		||||
// If is encoded in UTF8, the file don't need to be handled, then return the original path directly.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -240,6 +240,7 @@ static constexpr const char* LAST_TRIANGLE_ID_ATTR = "lastid";
 | 
			
		|||
static constexpr const char* SUBTYPE_ATTR = "subtype";
 | 
			
		||||
static constexpr const char* LOCK_ATTR = "locked";
 | 
			
		||||
static constexpr const char* BED_TYPE_ATTR = "bed_type";
 | 
			
		||||
static constexpr const char* PRINT_SEQUENCE_ATTR = "print_sequence";
 | 
			
		||||
static constexpr const char* GCODE_FILE_ATTR = "gcode_file";
 | 
			
		||||
static constexpr const char* THUMBNAIL_FILE_ATTR = "thumbnail_file";
 | 
			
		||||
static constexpr const char* PATTERN_FILE_ATTR = "pattern_file";
 | 
			
		||||
| 
						 | 
				
			
			@ -1776,6 +1777,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
 | 
			
		|||
            plate_data_list[it->first-1]->pattern_file = (m_load_restore || it->second->pattern_file.empty()) ? it->second->pattern_file : m_backup_path + "/" + it->second->pattern_file;
 | 
			
		||||
            plate_data_list[it->first-1]->pattern_bbox_file = (m_load_restore || it->second->pattern_bbox_file.empty()) ? it->second->pattern_bbox_file : m_backup_path + "/" + it->second->pattern_bbox_file;
 | 
			
		||||
            plate_data_list[it->first-1]->config = it->second->config;
 | 
			
		||||
 | 
			
		||||
            current_plate_data = plate_data_list[it->first - 1];
 | 
			
		||||
            BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", plate %1%, thumbnail_file=%2%")%it->first %plate_data_list[it->first-1]->thumbnail_file;
 | 
			
		||||
            it++;
 | 
			
		||||
| 
						 | 
				
			
			@ -2499,6 +2501,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
 | 
			
		|||
 | 
			
		||||
    void _BBS_3MF_Importer::_extract_custom_gcode_per_print_z_from_archive(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat)
 | 
			
		||||
    {
 | 
			
		||||
        //BBS: add plate tree related logic
 | 
			
		||||
        if (stat.m_uncomp_size > 0) {
 | 
			
		||||
            std::string buffer((size_t)stat.m_uncomp_size, 0);
 | 
			
		||||
            mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
 | 
			
		||||
| 
						 | 
				
			
			@ -2513,45 +2516,71 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
 | 
			
		|||
 | 
			
		||||
            if (main_tree.front().first != "custom_gcodes_per_layer")
 | 
			
		||||
                return;
 | 
			
		||||
            pt::ptree code_tree = main_tree.front().second;
 | 
			
		||||
 | 
			
		||||
            m_model->custom_gcode_per_print_z.gcodes.clear();
 | 
			
		||||
            auto extract_code = [this](int plate_id, pt::ptree code_tree) {
 | 
			
		||||
                for (const auto& code : code_tree) {
 | 
			
		||||
                    if (code.first == "mode") {
 | 
			
		||||
                        pt::ptree tree = code.second;
 | 
			
		||||
                        std::string mode = tree.get<std::string>("<xmlattr>.value");
 | 
			
		||||
                        m_model->plates_custom_gcodes[plate_id - 1].mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder :
 | 
			
		||||
                            mode == CustomGCode::MultiAsSingleMode ? CustomGCode::Mode::MultiAsSingle :
 | 
			
		||||
                            CustomGCode::Mode::MultiExtruder;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (code.first == "layer") {
 | 
			
		||||
                        pt::ptree tree = code.second;
 | 
			
		||||
                        double print_z = tree.get<double>("<xmlattr>.top_z");
 | 
			
		||||
                        int extruder = tree.get<int>("<xmlattr>.extruder");
 | 
			
		||||
                        std::string color = tree.get<std::string>("<xmlattr>.color");
 | 
			
		||||
 | 
			
		||||
            for (const auto& code : code_tree) {
 | 
			
		||||
                if (code.first == "mode") {
 | 
			
		||||
                    pt::ptree tree = code.second;
 | 
			
		||||
                    std::string mode = tree.get<std::string>("<xmlattr>.value");
 | 
			
		||||
                    m_model->custom_gcode_per_print_z.mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder :
 | 
			
		||||
                                                             mode == CustomGCode::MultiAsSingleMode  ? CustomGCode::Mode::MultiAsSingle  :
 | 
			
		||||
                                                             CustomGCode::Mode::MultiExtruder;
 | 
			
		||||
                        CustomGCode::Type   type;
 | 
			
		||||
                        std::string         extra;
 | 
			
		||||
                        pt::ptree attr_tree = tree.find("<xmlattr>")->second;
 | 
			
		||||
                        if (attr_tree.find("type") == attr_tree.not_found()) {
 | 
			
		||||
                            // It means that data was saved in old version (2.2.0 and older) of PrusaSlicer
 | 
			
		||||
                            // read old data ...
 | 
			
		||||
                            std::string gcode = tree.get<std::string>("<xmlattr>.gcode");
 | 
			
		||||
                            // ... and interpret them to the new data
 | 
			
		||||
                            type = gcode == "M600" ? CustomGCode::ColorChange :
 | 
			
		||||
                                gcode == "M601" ? CustomGCode::PausePrint :
 | 
			
		||||
                                gcode == "tool_change" ? CustomGCode::ToolChange : CustomGCode::Custom;
 | 
			
		||||
                            extra = type == CustomGCode::PausePrint ? color :
 | 
			
		||||
                                type == CustomGCode::Custom ? gcode : "";
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            type = static_cast<CustomGCode::Type>(tree.get<int>("<xmlattr>.type"));
 | 
			
		||||
                            extra = tree.get<std::string>("<xmlattr>.extra");
 | 
			
		||||
                        }
 | 
			
		||||
                        m_model->plates_custom_gcodes[plate_id - 1].gcodes.push_back(CustomGCode::Item{ print_z, type, extruder, color, extra });
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (code.first != "layer")
 | 
			
		||||
                    continue;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
                pt::ptree tree = code.second;
 | 
			
		||||
                double print_z          = tree.get<double>      ("<xmlattr>.top_z" );
 | 
			
		||||
                int extruder            = tree.get<int>         ("<xmlattr>.extruder");
 | 
			
		||||
                std::string color       = tree.get<std::string> ("<xmlattr>.color"   );
 | 
			
		||||
            m_model->plates_custom_gcodes.clear();
 | 
			
		||||
 | 
			
		||||
                CustomGCode::Type   type;
 | 
			
		||||
                std::string         extra;
 | 
			
		||||
                pt::ptree attr_tree = tree.find("<xmlattr>")->second;
 | 
			
		||||
                if (attr_tree.find("type") == attr_tree.not_found()) {
 | 
			
		||||
                    // It means that data was saved in old version (2.2.0 and older) of PrusaSlicer
 | 
			
		||||
                    // read old data ...
 | 
			
		||||
                    std::string gcode       = tree.get<std::string> ("<xmlattr>.gcode");
 | 
			
		||||
                    // ... and interpret them to the new data
 | 
			
		||||
                    type  = gcode == "M600"           ? CustomGCode::ColorChange :
 | 
			
		||||
                            gcode == "M601"           ? CustomGCode::PausePrint  :
 | 
			
		||||
                            gcode == "tool_change"    ? CustomGCode::ToolChange  :   CustomGCode::Custom;
 | 
			
		||||
                    extra = type == CustomGCode::PausePrint ? color :
 | 
			
		||||
                            type == CustomGCode::Custom     ? gcode : "";
 | 
			
		||||
            bool has_plate_info = false;
 | 
			
		||||
            for (const auto& element : main_tree.front().second) {
 | 
			
		||||
                if (element.first == "plate") {
 | 
			
		||||
                    has_plate_info = true;
 | 
			
		||||
 | 
			
		||||
                    int plate_id = -1;
 | 
			
		||||
                    pt::ptree code_tree = element.second;
 | 
			
		||||
                    for (const auto& code : code_tree) {
 | 
			
		||||
                        if (code.first == "plate_info") {
 | 
			
		||||
                            plate_id = code.second.get<int>("<xmlattr>.id");
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                    }
 | 
			
		||||
                    if (plate_id == -1)
 | 
			
		||||
                        continue;
 | 
			
		||||
 | 
			
		||||
                    extract_code(plate_id, code_tree);
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    type  = static_cast<CustomGCode::Type>(tree.get<int>("<xmlattr>.type"));
 | 
			
		||||
                    extra = tree.get<std::string>("<xmlattr>.extra");
 | 
			
		||||
                }
 | 
			
		||||
                m_model->custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra}) ;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!has_plate_info) {
 | 
			
		||||
                int plate_id = 1;
 | 
			
		||||
                pt::ptree code_tree = main_tree.front().second;
 | 
			
		||||
                extract_code(plate_id, code_tree);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -3444,6 +3473,12 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
 | 
			
		|||
                ConfigOptionEnum<BedType>::from_string(value, bed_type);
 | 
			
		||||
                m_curr_plater->config.set_key_value("curr_bed_type", new ConfigOptionEnum<BedType>(bed_type));
 | 
			
		||||
            }
 | 
			
		||||
            else if (key == PRINT_SEQUENCE_ATTR)
 | 
			
		||||
            {
 | 
			
		||||
                PrintSequence print_sequence = PrintSequence::ByLayer;
 | 
			
		||||
                ConfigOptionEnum<PrintSequence>::from_string(value, print_sequence);
 | 
			
		||||
                m_curr_plater->config.set_key_value("print_sequence", new ConfigOptionEnum<PrintSequence>(print_sequence));
 | 
			
		||||
            }
 | 
			
		||||
            else if (key == GCODE_FILE_ATTR)
 | 
			
		||||
            {
 | 
			
		||||
                m_curr_plater->gcode_file = value;
 | 
			
		||||
| 
						 | 
				
			
			@ -3805,6 +3840,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
 | 
			
		|||
                        //add the shared mesh logic
 | 
			
		||||
                        shared_mesh_id = ::atoi(metadata.value.c_str());
 | 
			
		||||
                        found_count++;
 | 
			
		||||
                        BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": line %1%, shared_mesh_id %2%")%__LINE__%shared_mesh_id;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (found_count >= 2)
 | 
			
		||||
| 
						 | 
				
			
			@ -3822,6 +3858,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
 | 
			
		|||
                std::map<int, ModelVolume*>::iterator iter = m_shared_meshes.find(shared_mesh_id);
 | 
			
		||||
                if (iter != m_shared_meshes.end()) {
 | 
			
		||||
                    shared_volume = iter->second;
 | 
			
		||||
                    BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": line %1%, found shared mesh, id %2%, mesh %3%")%__LINE__%shared_mesh_id%shared_volume;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3874,11 +3911,17 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
 | 
			
		|||
 | 
			
		||||
                volume = object.add_volume(std::move(triangle_mesh));
 | 
			
		||||
 | 
			
		||||
                m_shared_meshes[sub_object->id] = volume;
 | 
			
		||||
                if (shared_mesh_id != -1)
 | 
			
		||||
                    //for some cases the shared mesh is in other plate and not loaded in cli slicing
 | 
			
		||||
                    //we need to use the first one in the same plate instead
 | 
			
		||||
                    m_shared_meshes[shared_mesh_id] = volume;
 | 
			
		||||
                else
 | 
			
		||||
                    m_shared_meshes[sub_object->id] = volume;
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                //create volume to use shared mesh
 | 
			
		||||
                volume = object.add_volume_with_shared_mesh(*shared_volume);
 | 
			
		||||
                BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": line %1%, create volume using shared_mesh %2%")%__LINE__%shared_volume;
 | 
			
		||||
            }
 | 
			
		||||
            // stores the volume matrix taken from the metadata, if present
 | 
			
		||||
            if (has_transform)
 | 
			
		||||
| 
						 | 
				
			
			@ -5347,6 +5390,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
 | 
			
		|||
        bool sub_model = !objects_data.empty();
 | 
			
		||||
        bool write_object = sub_model || !m_split_model;
 | 
			
		||||
 | 
			
		||||
        BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", filename %1%, m_split_model %2%,  sub_model %3%")%filename % m_split_model % sub_model;
 | 
			
		||||
 | 
			
		||||
#if WRITE_ZIP_LANGUAGE_ENCODING
 | 
			
		||||
        auto & zip_filename = filename;
 | 
			
		||||
#else
 | 
			
		||||
| 
						 | 
				
			
			@ -6388,6 +6433,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
 | 
			
		|||
                if (bed_type_opt != nullptr && bed_type_names.size() > bed_type_opt->getInt())
 | 
			
		||||
                    stream << "    <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << BED_TYPE_ATTR << "\" " << VALUE_ATTR << "=\"" << bed_type_names[bed_type_opt->getInt()] << "\"/>\n";
 | 
			
		||||
 | 
			
		||||
                ConfigOption* print_sequence_opt = plate_data->config.option("print_sequence");
 | 
			
		||||
                t_config_enum_names print_sequence_names = ConfigOptionEnum<PrintSequence>::get_enum_names();
 | 
			
		||||
                if (print_sequence_opt != nullptr && print_sequence_names.size() > print_sequence_opt->getInt())
 | 
			
		||||
                    stream << "    <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << PRINT_SEQUENCE_ATTR << "\" " << VALUE_ATTR << "=\"" << print_sequence_names[print_sequence_opt->getInt()] << "\"/>\n";
 | 
			
		||||
 | 
			
		||||
                if (save_gcode)
 | 
			
		||||
                    stream << "    <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << GCODE_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << xml_escape(plate_data->gcode_file) << "\"/>\n";
 | 
			
		||||
                if (!plate_data->gcode_file.empty()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -6621,46 +6671,50 @@ bool _BBS_3MF_Exporter::_add_gcode_file_to_archive(mz_zip_archive& archive, cons
 | 
			
		|||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool _BBS_3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config)
 | 
			
		||||
bool _BBS_3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config)
 | 
			
		||||
{
 | 
			
		||||
    //BBS: add plate tree related logic
 | 
			
		||||
    std::string out = "";
 | 
			
		||||
 | 
			
		||||
    if (!model.custom_gcode_per_print_z.gcodes.empty()) {
 | 
			
		||||
        pt::ptree tree;
 | 
			
		||||
        pt::ptree& main_tree = tree.add("custom_gcodes_per_layer", "");
 | 
			
		||||
 | 
			
		||||
        for (const CustomGCode::Item& code : model.custom_gcode_per_print_z.gcodes) {
 | 
			
		||||
            pt::ptree& code_tree = main_tree.add("layer", "");
 | 
			
		||||
    bool has_custom_gcode = false;
 | 
			
		||||
    pt::ptree tree;
 | 
			
		||||
    pt::ptree& main_tree = tree.add("custom_gcodes_per_layer", "");
 | 
			
		||||
    for (auto custom_gcodes : model.plates_custom_gcodes) {
 | 
			
		||||
            has_custom_gcode = true;
 | 
			
		||||
            pt::ptree& plate_tree = main_tree.add("plate", "");
 | 
			
		||||
            pt::ptree& plate_idx_tree = plate_tree.add("plate_info", "");
 | 
			
		||||
            plate_idx_tree.put("<xmlattr>.id", custom_gcodes.first + 1);
 | 
			
		||||
 | 
			
		||||
            // store data of custom_gcode_per_print_z
 | 
			
		||||
            code_tree.put("<xmlattr>.top_z"   , code.print_z  );
 | 
			
		||||
            code_tree.put("<xmlattr>.type"      , static_cast<int>(code.type));
 | 
			
		||||
            code_tree.put("<xmlattr>.extruder"  , code.extruder );
 | 
			
		||||
            code_tree.put("<xmlattr>.color"     , code.color    );
 | 
			
		||||
            code_tree.put("<xmlattr>.extra"     , code.extra    );
 | 
			
		||||
            for (const CustomGCode::Item& code : custom_gcodes.second.gcodes) {
 | 
			
		||||
                pt::ptree& code_tree = plate_tree.add("layer", "");
 | 
			
		||||
                code_tree.put("<xmlattr>.top_z", code.print_z);
 | 
			
		||||
                code_tree.put("<xmlattr>.type", static_cast<int>(code.type));
 | 
			
		||||
                code_tree.put("<xmlattr>.extruder", code.extruder);
 | 
			
		||||
                code_tree.put("<xmlattr>.color", code.color);
 | 
			
		||||
                code_tree.put("<xmlattr>.extra", code.extra);
 | 
			
		||||
 | 
			
		||||
            //BBS
 | 
			
		||||
            std::string gcode = //code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode")    :
 | 
			
		||||
                                code.type == CustomGCode::PausePrint  ? config->opt_string("machine_pause_gcode")     :
 | 
			
		||||
                                code.type == CustomGCode::Template    ? config->opt_string("template_custom_gcode") :
 | 
			
		||||
                                code.type == CustomGCode::ToolChange  ? "tool_change"   : code.extra;
 | 
			
		||||
            code_tree.put("<xmlattr>.gcode"     , gcode   );
 | 
			
		||||
        }
 | 
			
		||||
                //BBS
 | 
			
		||||
                std::string gcode = //code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode")    :
 | 
			
		||||
                    code.type == CustomGCode::PausePrint ? config->opt_string("machine_pause_gcode") :
 | 
			
		||||
                    code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") :
 | 
			
		||||
                    code.type == CustomGCode::ToolChange ? "tool_change" : code.extra;
 | 
			
		||||
                code_tree.put("<xmlattr>.gcode", gcode);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        pt::ptree& mode_tree = main_tree.add("mode", "");
 | 
			
		||||
        // store mode of a custom_gcode_per_print_z
 | 
			
		||||
        mode_tree.put("<xmlattr>.value", model.custom_gcode_per_print_z.mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode :
 | 
			
		||||
                                         model.custom_gcode_per_print_z.mode == CustomGCode::Mode::MultiAsSingle ?  CustomGCode::MultiAsSingleMode :
 | 
			
		||||
                                         CustomGCode::MultiExtruderMode);
 | 
			
		||||
            pt::ptree& mode_tree = plate_tree.add("mode", "");
 | 
			
		||||
            // store mode of a custom_gcode_per_print_z
 | 
			
		||||
            mode_tree.put("<xmlattr>.value", custom_gcodes.second.mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode :
 | 
			
		||||
                custom_gcodes.second.mode == CustomGCode::Mode::MultiAsSingle ? CustomGCode::MultiAsSingleMode :
 | 
			
		||||
                CustomGCode::MultiExtruderMode);
 | 
			
		||||
 | 
			
		||||
        if (!tree.empty()) {
 | 
			
		||||
            std::ostringstream oss;
 | 
			
		||||
            boost::property_tree::write_xml(oss, tree);
 | 
			
		||||
            out = oss.str();
 | 
			
		||||
    }
 | 
			
		||||
    if (has_custom_gcode) {
 | 
			
		||||
        std::ostringstream oss;
 | 
			
		||||
        boost::property_tree::write_xml(oss, tree);
 | 
			
		||||
        out = oss.str();
 | 
			
		||||
 | 
			
		||||
            // Post processing("beautification") of the output string
 | 
			
		||||
            boost::replace_all(out, "><", ">\n<");
 | 
			
		||||
        }
 | 
			
		||||
        // Post processing("beautification") of the output string
 | 
			
		||||
        boost::replace_all(out, "><", ">\n<");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!out.empty()) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,9 @@ struct ThumbnailData;
 | 
			
		|||
#define EMBEDDED_FILAMENT_FILE_FORMAT      "Metadata/filament_settings_%1%.config"
 | 
			
		||||
#define EMBEDDED_PRINTER_FILE_FORMAT      "Metadata/machine_settings_%1%.config"
 | 
			
		||||
 | 
			
		||||
#define BBL_DESIGNER_PROFILE_ID_TAG      "DesignProfileId"
 | 
			
		||||
#define BBL_DESIGNER_MODEL_ID_TAG        "DesignModelId"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//BBS: define assistant struct to store temporary variable during exporting 3mf
 | 
			
		||||
class PackingTemporaryData
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -82,6 +82,132 @@ static const int g_max_flush_count = 4;
 | 
			
		|||
 | 
			
		||||
bool GCode::gcode_label_objects = true;
 | 
			
		||||
 | 
			
		||||
Vec2d travel_point_1;
 | 
			
		||||
Vec2d travel_point_2;
 | 
			
		||||
Vec2d travel_point_3;
 | 
			
		||||
 | 
			
		||||
static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
 | 
			
		||||
{
 | 
			
		||||
    // give safe value in case there is no start_end_points in config
 | 
			
		||||
    std::vector<Vec2d> out_points;
 | 
			
		||||
    out_points.emplace_back(Vec2d(54, 0));
 | 
			
		||||
    out_points.emplace_back(Vec2d(54, 0));
 | 
			
		||||
    out_points.emplace_back(Vec2d(54, 245));
 | 
			
		||||
 | 
			
		||||
    // get the start_end_points from config (20, -3) (54, 245)
 | 
			
		||||
    Pointfs points = print.config().start_end_points.values;
 | 
			
		||||
    if (points.size() != 2)
 | 
			
		||||
        return out_points;
 | 
			
		||||
 | 
			
		||||
    Vec2d start_point  = points[0];
 | 
			
		||||
    Vec2d end_point    = points[1];
 | 
			
		||||
 | 
			
		||||
    // the cutter area size(18, 28)
 | 
			
		||||
    Pointfs excluse_area = print.config().bed_exclude_area.values;
 | 
			
		||||
    if (excluse_area.size() != 4)
 | 
			
		||||
        return out_points;
 | 
			
		||||
 | 
			
		||||
    double cutter_area_x = excluse_area[2].x() + 2;
 | 
			
		||||
    double cutter_area_y = excluse_area[2].y() + 2;
 | 
			
		||||
 | 
			
		||||
    double start_x_position = start_point.x();
 | 
			
		||||
    double end_x_position   = end_point.x();
 | 
			
		||||
    double end_y_position   = end_point.y();
 | 
			
		||||
 | 
			
		||||
    bool can_travel_form_left = true;
 | 
			
		||||
 | 
			
		||||
    // step 1: get the x-range intervals of all objects
 | 
			
		||||
    std::vector<std::pair<double, double>> object_intervals;
 | 
			
		||||
    for (PrintObject *print_object : print.objects()) {
 | 
			
		||||
        const PrintInstances &print_instances = print_object->instances();
 | 
			
		||||
        BoundingBoxf3 bounding_box = print_instances[0].model_instance->get_object()->bounding_box();
 | 
			
		||||
 | 
			
		||||
        if (bounding_box.min.x() < start_x_position && bounding_box.min.y() < cutter_area_y)
 | 
			
		||||
            can_travel_form_left = false;
 | 
			
		||||
 | 
			
		||||
        std::pair<double, double> object_scope = std::make_pair(bounding_box.min.x() - 2, bounding_box.max.x() + 2);
 | 
			
		||||
        if (object_intervals.empty())
 | 
			
		||||
            object_intervals.push_back(object_scope);
 | 
			
		||||
        else {
 | 
			
		||||
            std::vector<std::pair<double, double>> new_object_intervals;
 | 
			
		||||
            bool intervals_intersect = false;
 | 
			
		||||
            std::pair<double, double>              new_merged_scope;
 | 
			
		||||
            for (auto object_interval : object_intervals) {
 | 
			
		||||
                if (object_interval.second >= object_scope.first && object_interval.first <= object_scope.second) {
 | 
			
		||||
                    if (intervals_intersect) {
 | 
			
		||||
                        new_merged_scope = std::make_pair(std::min(object_interval.first, new_merged_scope.first), std::max(object_interval.second, new_merged_scope.second));
 | 
			
		||||
                    } else { // it is the first intersection
 | 
			
		||||
                        new_merged_scope = std::make_pair(std::min(object_interval.first, object_scope.first), std::max(object_interval.second, object_scope.second));
 | 
			
		||||
                    }
 | 
			
		||||
                    intervals_intersect = true;
 | 
			
		||||
                } else {
 | 
			
		||||
                    new_object_intervals.push_back(object_interval);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (intervals_intersect) {
 | 
			
		||||
                new_object_intervals.push_back(new_merged_scope);
 | 
			
		||||
                object_intervals = new_object_intervals;
 | 
			
		||||
            } else
 | 
			
		||||
                object_intervals.push_back(object_scope);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // step 2: get the available x-range
 | 
			
		||||
    std::sort(object_intervals.begin(), object_intervals.end(),
 | 
			
		||||
              [](const std::pair<double, double> &left, const std::pair<double, double> &right) {
 | 
			
		||||
            return left.first < right.first;
 | 
			
		||||
    });
 | 
			
		||||
    std::vector<std::pair<double, double>> available_intervals;
 | 
			
		||||
    double                                 start_position = 0;
 | 
			
		||||
    for (auto object_interval : object_intervals) {
 | 
			
		||||
        if (object_interval.first > start_position)
 | 
			
		||||
            available_intervals.push_back(std::make_pair(start_position, object_interval.first));
 | 
			
		||||
        start_position = object_interval.second;
 | 
			
		||||
    }
 | 
			
		||||
    available_intervals.push_back(std::make_pair(start_position, 255));
 | 
			
		||||
 | 
			
		||||
    // step 3: get the nearest path
 | 
			
		||||
    double new_path     = 255;
 | 
			
		||||
    for (auto available_interval : available_intervals) {
 | 
			
		||||
        if (available_interval.first > end_x_position) {
 | 
			
		||||
            double distance = available_interval.first - end_x_position;
 | 
			
		||||
            new_path        = abs(end_x_position - new_path) < distance ? new_path : available_interval.first;
 | 
			
		||||
            break;
 | 
			
		||||
        } else {
 | 
			
		||||
            if (available_interval.second >= end_x_position) {
 | 
			
		||||
                new_path = end_x_position;
 | 
			
		||||
                break;
 | 
			
		||||
            } else if (!can_travel_form_left && available_interval.second < start_x_position) {
 | 
			
		||||
                continue;
 | 
			
		||||
            } else {
 | 
			
		||||
                new_path     = available_interval.second;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // step 4: generate path points  (new_path == start_x_position means not need to change path)
 | 
			
		||||
    Vec2d out_point_1;
 | 
			
		||||
    Vec2d out_point_2;
 | 
			
		||||
    Vec2d out_point_3;
 | 
			
		||||
    if (new_path < start_x_position) {
 | 
			
		||||
        out_point_1 = Vec2d(start_x_position, cutter_area_y);
 | 
			
		||||
        out_point_2 = Vec2d(new_path, cutter_area_y);
 | 
			
		||||
        out_point_3 = Vec2d(new_path, end_y_position);
 | 
			
		||||
    } else {
 | 
			
		||||
        out_point_1 = Vec2d(new_path, 0);
 | 
			
		||||
        out_point_2 = Vec2d(new_path, 0);
 | 
			
		||||
        out_point_3 = Vec2d(new_path, end_y_position);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    out_points.clear();
 | 
			
		||||
    out_points.emplace_back(out_point_1);
 | 
			
		||||
    out_points.emplace_back(out_point_2);
 | 
			
		||||
    out_points.emplace_back(out_point_3);
 | 
			
		||||
 | 
			
		||||
    return out_points;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Only add a newline in case the current G-code does not end with a newline.
 | 
			
		||||
    static inline void check_add_eol(std::string& gcode)
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -378,6 +504,12 @@ bool GCode::gcode_label_objects = true;
 | 
			
		|||
                config.set_key_value("second_flush_volume", new ConfigOptionFloat(purge_length / 2.f));
 | 
			
		||||
                config.set_key_value("old_filament_e_feedrate", new ConfigOptionInt(old_filament_e_feedrate));
 | 
			
		||||
                config.set_key_value("new_filament_e_feedrate", new ConfigOptionInt(new_filament_e_feedrate));
 | 
			
		||||
                config.set_key_value("travel_point_1_x", new ConfigOptionFloat(float(travel_point_1.x())));
 | 
			
		||||
                config.set_key_value("travel_point_1_y", new ConfigOptionFloat(float(travel_point_1.y())));
 | 
			
		||||
                config.set_key_value("travel_point_2_x", new ConfigOptionFloat(float(travel_point_2.x())));
 | 
			
		||||
                config.set_key_value("travel_point_2_y", new ConfigOptionFloat(float(travel_point_2.y())));
 | 
			
		||||
                config.set_key_value("travel_point_3_x", new ConfigOptionFloat(float(travel_point_3.x())));
 | 
			
		||||
                config.set_key_value("travel_point_3_y", new ConfigOptionFloat(float(travel_point_3.y())));
 | 
			
		||||
 | 
			
		||||
                int flush_count = std::min(g_max_flush_count, (int)std::round(purge_volume / g_purge_volume_one_time));
 | 
			
		||||
                float flush_unit = purge_length / flush_count;
 | 
			
		||||
| 
						 | 
				
			
			@ -624,7 +756,7 @@ bool GCode::gcode_label_objects = true;
 | 
			
		|||
std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObject& object)
 | 
			
		||||
{
 | 
			
		||||
    std::vector<GCode::LayerToPrint> layers_to_print;
 | 
			
		||||
    layers_to_print.reserve(object.layers().size() + object.support_layers().size() + object.tree_support_layers().size());
 | 
			
		||||
    layers_to_print.reserve(object.layers().size() + object.support_layers().size());
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um.
 | 
			
		||||
| 
						 | 
				
			
			@ -647,9 +779,8 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
 | 
			
		|||
    // Pair the object layers with the support layers by z.
 | 
			
		||||
    size_t idx_object_layer = 0;
 | 
			
		||||
    size_t idx_support_layer = 0;
 | 
			
		||||
    size_t idx_tree_support_layer = 0;
 | 
			
		||||
    const LayerToPrint* last_extrusion_layer = nullptr;
 | 
			
		||||
    while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size() || idx_tree_support_layer < object.tree_support_layers().size()) {
 | 
			
		||||
    while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) {
 | 
			
		||||
        LayerToPrint layer_to_print;
 | 
			
		||||
        double print_z_min = std::numeric_limits<double>::max();
 | 
			
		||||
        if (idx_object_layer < object.layers().size()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -662,11 +793,6 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
 | 
			
		|||
            print_z_min = std::min(print_z_min, layer_to_print.support_layer->print_z);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (idx_tree_support_layer < object.tree_support_layers().size()) {
 | 
			
		||||
            layer_to_print.tree_support_layer = object.tree_support_layers()[idx_tree_support_layer++];
 | 
			
		||||
            print_z_min = std::min(print_z_min, layer_to_print.tree_support_layer->print_z);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (layer_to_print.object_layer && layer_to_print.object_layer->print_z > print_z_min + EPSILON) {
 | 
			
		||||
            layer_to_print.object_layer = nullptr;
 | 
			
		||||
            --idx_object_layer;
 | 
			
		||||
| 
						 | 
				
			
			@ -677,17 +803,11 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
 | 
			
		|||
            --idx_support_layer;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (layer_to_print.tree_support_layer && layer_to_print.tree_support_layer->print_z > print_z_min + EPSILON) {
 | 
			
		||||
            layer_to_print.tree_support_layer = nullptr;
 | 
			
		||||
            --idx_tree_support_layer;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        layer_to_print.original_object = &object;
 | 
			
		||||
        layers_to_print.push_back(layer_to_print);
 | 
			
		||||
 | 
			
		||||
        bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions())
 | 
			
		||||
            || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions()
 | 
			
		||||
            || (layer_to_print.tree_support_layer && layer_to_print.tree_support_layer->has_extrusions()));
 | 
			
		||||
            || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions());
 | 
			
		||||
 | 
			
		||||
        // Check that there are extrusions on the very first layer. The case with empty
 | 
			
		||||
        // first layer may result in skirt/brim in the air and maybe other issues.
 | 
			
		||||
| 
						 | 
				
			
			@ -699,13 +819,12 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
 | 
			
		|||
        // In case there are extrusions on this layer, check there is a layer to lay it on.
 | 
			
		||||
        if ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions())
 | 
			
		||||
            // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions.
 | 
			
		||||
            || (layer_to_print.support_layer /* && layer_to_print.support_layer->has_extrusions() */)
 | 
			
		||||
            || (layer_to_print.tree_support_layer)) {
 | 
			
		||||
            || (layer_to_print.support_layer /* && layer_to_print.support_layer->has_extrusions() */)) {
 | 
			
		||||
            double top_cd = object.config().support_top_z_distance;
 | 
			
		||||
            //double bottom_cd = object.config().support_bottom_z_distance == 0. ? top_cd : object.config().support_bottom_z_distance;
 | 
			
		||||
            double bottom_cd = top_cd;
 | 
			
		||||
 | 
			
		||||
            double extra_gap = ((layer_to_print.support_layer || layer_to_print.tree_support_layer) ? bottom_cd : top_cd);
 | 
			
		||||
            double extra_gap = (layer_to_print.support_layer ? bottom_cd : top_cd);
 | 
			
		||||
 | 
			
		||||
            double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.)
 | 
			
		||||
                + layer_to_print.layer()->height
 | 
			
		||||
| 
						 | 
				
			
			@ -914,6 +1033,9 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
 | 
			
		|||
{
 | 
			
		||||
    PROFILE_CLEAR();
 | 
			
		||||
 | 
			
		||||
    // BBS
 | 
			
		||||
    m_curr_print = print;
 | 
			
		||||
 | 
			
		||||
    CNumericLocalesSetter locales_setter;
 | 
			
		||||
 | 
			
		||||
    // Does the file exist? If so, we hope that it is still valid.
 | 
			
		||||
| 
						 | 
				
			
			@ -1333,7 +1455,14 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
 | 
			
		|||
            for (auto layer : object->support_layers())
 | 
			
		||||
                zs.push_back((coord_t)(layer->print_z / EPSILON));
 | 
			
		||||
            std::sort(zs.begin(), zs.end());
 | 
			
		||||
            m_layer_count += (unsigned int)(object->instances().size() * (std::unique(zs.begin(), zs.end()) - zs.begin()));
 | 
			
		||||
            //BBS: merge numerically very close Z values.
 | 
			
		||||
            auto end_it = std::unique(zs.begin(), zs.end());
 | 
			
		||||
            unsigned int temp_layer_count = (unsigned int)(end_it - zs.begin());
 | 
			
		||||
            for (auto it = zs.begin(); it != end_it - 1; it++) {
 | 
			
		||||
                if (abs(*it - *(it + 1)) < EPSILON)
 | 
			
		||||
                    temp_layer_count--;
 | 
			
		||||
            }
 | 
			
		||||
            m_layer_count += (unsigned int)(object->instances().size() * temp_layer_count);
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        // Print all objects with the same print_z together.
 | 
			
		||||
| 
						 | 
				
			
			@ -1347,7 +1476,13 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
 | 
			
		|||
                zs.push_back((coord_t)(layer->print_z / EPSILON));
 | 
			
		||||
        }
 | 
			
		||||
        std::sort(zs.begin(), zs.end());
 | 
			
		||||
        m_layer_count = (unsigned int)(std::unique(zs.begin(), zs.end()) - zs.begin());
 | 
			
		||||
        //BBS: merge numerically very close Z values.
 | 
			
		||||
        auto end_it = std::unique(zs.begin(), zs.end());
 | 
			
		||||
        m_layer_count = (unsigned int)(end_it - zs.begin());
 | 
			
		||||
        for (auto it = zs.begin(); it != end_it - 1; it++) {
 | 
			
		||||
            if (abs(*it - *(it + 1)) < EPSILON)
 | 
			
		||||
                m_layer_count--;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    print.throw_if_canceled();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1374,6 +1509,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
 | 
			
		|||
    //BBS: total estimated printing time
 | 
			
		||||
    file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str());
 | 
			
		||||
    //BBS: total layer number
 | 
			
		||||
    file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Layer_Number_Placeholder).c_str());
 | 
			
		||||
    file.write_format("; total layer number: %d\n", m_layer_count);
 | 
			
		||||
    file.write_format("; HEADER_BLOCK_END\n\n");
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1450,6 +1586,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
 | 
			
		|||
    // For a print by objects, find the 1st printing object.
 | 
			
		||||
    ToolOrdering tool_ordering;
 | 
			
		||||
    unsigned int initial_extruder_id = (unsigned int)-1;
 | 
			
		||||
    //BBS: first non-support filament extruder
 | 
			
		||||
    unsigned int initial_non_support_extruder_id;
 | 
			
		||||
    unsigned int final_extruder_id   = (unsigned int)-1;
 | 
			
		||||
    bool         has_wipe_tower      = false;
 | 
			
		||||
    std::vector<const PrintInstance*> 					print_object_instances_ordering;
 | 
			
		||||
| 
						 | 
				
			
			@ -1462,8 +1600,33 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
 | 
			
		|||
        print_object_instance_sequential_active = print_object_instances_ordering.begin();
 | 
			
		||||
        for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++ print_object_instance_sequential_active) {
 | 
			
		||||
            tool_ordering = ToolOrdering(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id);
 | 
			
		||||
            if ((initial_extruder_id = tool_ordering.first_extruder()) != static_cast<unsigned int>(-1))
 | 
			
		||||
            if ((initial_extruder_id = tool_ordering.first_extruder()) != static_cast<unsigned int>(-1)) {
 | 
			
		||||
                //BBS: try to find the non-support filament extruder if is multi color and initial_extruder is support filament
 | 
			
		||||
                initial_non_support_extruder_id = initial_extruder_id;
 | 
			
		||||
                if (tool_ordering.all_extruders().size() > 1 && print.config().filament_is_support.get_at(initial_extruder_id)) {
 | 
			
		||||
                    bool has_non_support_filament = false;
 | 
			
		||||
                    for (unsigned int extruder : tool_ordering.all_extruders()) {
 | 
			
		||||
                        if (!print.config().filament_is_support.get_at(extruder)) {
 | 
			
		||||
                            has_non_support_filament = true;
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    //BBS: find the non-support filament extruder of object
 | 
			
		||||
                    if (has_non_support_filament)
 | 
			
		||||
                        for (LayerTools layer_tools : tool_ordering.layer_tools()) {
 | 
			
		||||
                            if (!layer_tools.has_object)
 | 
			
		||||
                                continue;
 | 
			
		||||
                            for (unsigned int extruder : layer_tools.extruders) {
 | 
			
		||||
                                if (print.config().filament_is_support.get_at(extruder))
 | 
			
		||||
                                    continue;
 | 
			
		||||
                                initial_non_support_extruder_id = extruder;
 | 
			
		||||
                                break;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (initial_extruder_id == static_cast<unsigned int>(-1))
 | 
			
		||||
            // No object to print was found, cancel the G-code export.
 | 
			
		||||
| 
						 | 
				
			
			@ -1471,6 +1634,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
 | 
			
		|||
        // We don't allow switching of extruders per layer by Model::custom_gcode_per_print_z in sequential mode.
 | 
			
		||||
        // Use the extruder IDs collected from Regions.
 | 
			
		||||
        this->set_extruders(print.extruders());
 | 
			
		||||
 | 
			
		||||
        has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower();
 | 
			
		||||
    } else {
 | 
			
		||||
        // Find tool ordering for all the objects at once, and the initial extruder ID.
 | 
			
		||||
        // If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it.
 | 
			
		||||
| 
						 | 
				
			
			@ -1490,6 +1655,32 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
 | 
			
		|||
#else
 | 
			
		||||
        initial_extruder_id = tool_ordering.first_extruder();
 | 
			
		||||
#endif
 | 
			
		||||
        //BBS: try to find the non-support filament extruder if is multi color and initial_extruder is support filament
 | 
			
		||||
        if (initial_extruder_id != static_cast<unsigned int>(-1)) {
 | 
			
		||||
            initial_non_support_extruder_id = initial_extruder_id;
 | 
			
		||||
            if (tool_ordering.all_extruders().size() > 1 && print.config().filament_is_support.get_at(initial_extruder_id)) {
 | 
			
		||||
                bool has_non_support_filament = false;
 | 
			
		||||
                for (unsigned int extruder : tool_ordering.all_extruders()) {
 | 
			
		||||
                    if (!print.config().filament_is_support.get_at(extruder)) {
 | 
			
		||||
                        has_non_support_filament = true;
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                //BBS: find the non-support filament extruder of object
 | 
			
		||||
                if (has_non_support_filament)
 | 
			
		||||
                    for (LayerTools layer_tools : tool_ordering.layer_tools()) {
 | 
			
		||||
                        if (!layer_tools.has_object)
 | 
			
		||||
                            continue;
 | 
			
		||||
                        for (unsigned int extruder : layer_tools.extruders) {
 | 
			
		||||
                            if (print.config().filament_is_support.get_at(extruder))
 | 
			
		||||
                                continue;
 | 
			
		||||
                            initial_non_support_extruder_id = extruder;
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // In non-sequential print, the printing extruders may have been modified by the extruder switches stored in Model::custom_gcode_per_print_z.
 | 
			
		||||
        // Therefore initialize the printing extruders from there.
 | 
			
		||||
        this->set_extruders(tool_ordering.all_extruders());
 | 
			
		||||
| 
						 | 
				
			
			@ -1499,6 +1690,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
 | 
			
		|||
    if (initial_extruder_id == (unsigned int)-1) {
 | 
			
		||||
        // Nothing to print!
 | 
			
		||||
        initial_extruder_id = 0;
 | 
			
		||||
        initial_non_support_extruder_id = 0;
 | 
			
		||||
        final_extruder_id   = 0;
 | 
			
		||||
    } else {
 | 
			
		||||
        final_extruder_id = tool_ordering.last_extruder();
 | 
			
		||||
| 
						 | 
				
			
			@ -1522,6 +1714,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
 | 
			
		|||
    // Let the start-up script prime the 1st printing tool.
 | 
			
		||||
    m_placeholder_parser.set("initial_tool", initial_extruder_id);
 | 
			
		||||
    m_placeholder_parser.set("initial_extruder", initial_extruder_id);
 | 
			
		||||
    //BBS
 | 
			
		||||
    m_placeholder_parser.set("initial_no_support_tool", initial_non_support_extruder_id);
 | 
			
		||||
    m_placeholder_parser.set("initial_no_support_extruder", initial_non_support_extruder_id);
 | 
			
		||||
    m_placeholder_parser.set("current_extruder", initial_extruder_id);
 | 
			
		||||
    //Set variable for total layer count so it can be used in custom gcode.
 | 
			
		||||
    m_placeholder_parser.set("total_layer_count", m_layer_count);
 | 
			
		||||
| 
						 | 
				
			
			@ -1531,10 +1726,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
 | 
			
		|||
    m_placeholder_parser.set("has_wipe_tower", has_wipe_tower);
 | 
			
		||||
    //m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config().single_extruder_multi_material_priming);
 | 
			
		||||
    m_placeholder_parser.set("total_toolchanges", std::max(0, print.wipe_tower_data().number_of_toolchanges)); // Check for negative toolchanges (single extruder mode) and set to 0 (no tool change).
 | 
			
		||||
    Vec2f plate_offset = m_writer.get_xy_offset();
 | 
			
		||||
    {
 | 
			
		||||
        BoundingBoxf bbox(print.config().printable_area.values);
 | 
			
		||||
        m_placeholder_parser.set("print_bed_min",  new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() }));
 | 
			
		||||
        m_placeholder_parser.set("print_bed_max",  new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() }));
 | 
			
		||||
        m_placeholder_parser.set("print_bed_min", new ConfigOptionFloats({ bbox.min.x() - plate_offset.x(), bbox.min.y() - plate_offset.y() }));
 | 
			
		||||
        m_placeholder_parser.set("print_bed_max", new ConfigOptionFloats({ bbox.max.x() - plate_offset.x(), bbox.max.y() - plate_offset.y() }));
 | 
			
		||||
        m_placeholder_parser.set("print_bed_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() }));
 | 
			
		||||
    }
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -1549,8 +1745,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
 | 
			
		|||
            pts->values.emplace_back(unscale(pt) - Vec2d(print.get_plate_origin().x(), print.get_plate_origin().y()));
 | 
			
		||||
        BoundingBoxf bbox(pts->values);
 | 
			
		||||
        m_placeholder_parser.set("first_layer_print_convex_hull", pts.release());
 | 
			
		||||
        m_placeholder_parser.set("first_layer_print_min",  new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() }));
 | 
			
		||||
        m_placeholder_parser.set("first_layer_print_max",  new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() }));
 | 
			
		||||
        m_placeholder_parser.set("first_layer_print_min", new ConfigOptionFloats({bbox.min.x() - plate_offset.x(), bbox.min.y() - plate_offset.y()}));
 | 
			
		||||
        m_placeholder_parser.set("first_layer_print_max", new ConfigOptionFloats({bbox.max.x() - plate_offset.x(), bbox.max.y() - plate_offset.y()}));
 | 
			
		||||
        m_placeholder_parser.set("first_layer_print_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() }));
 | 
			
		||||
    }
 | 
			
		||||
    float outer_wall_volumetric_speed = 0.0f;
 | 
			
		||||
| 
						 | 
				
			
			@ -1575,13 +1771,13 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
 | 
			
		|||
 | 
			
		||||
        //BBS: calculate the volumetric speed of outer wall. Ignore pre-object setting and multi-filament, and just use the default setting
 | 
			
		||||
        {
 | 
			
		||||
            float filament_max_volumetric_speed = m_config.option<ConfigOptionFloats>("filament_max_volumetric_speed")->get_at(initial_extruder_id);
 | 
			
		||||
            float filament_max_volumetric_speed = m_config.option<ConfigOptionFloats>("filament_max_volumetric_speed")->get_at(initial_non_support_extruder_id);
 | 
			
		||||
            float outer_wall_line_width = print.default_region_config().outer_wall_line_width.value;
 | 
			
		||||
            if (outer_wall_line_width == 0.0) {
 | 
			
		||||
                float default_line_width = print.default_object_config().line_width.value;
 | 
			
		||||
                outer_wall_line_width = default_line_width == 0.0 ? m_config.nozzle_diameter.get_at(initial_extruder_id) : default_line_width;
 | 
			
		||||
                outer_wall_line_width = default_line_width == 0.0 ? m_config.nozzle_diameter.get_at(initial_non_support_extruder_id) : default_line_width;
 | 
			
		||||
            }
 | 
			
		||||
            Flow outer_wall_flow = Flow(outer_wall_line_width, m_config.layer_height, m_config.nozzle_diameter.get_at(initial_extruder_id));
 | 
			
		||||
            Flow outer_wall_flow = Flow(outer_wall_line_width, m_config.layer_height, m_config.nozzle_diameter.get_at(initial_non_support_extruder_id));
 | 
			
		||||
            float outer_wall_speed = print.default_region_config().outer_wall_speed.value;
 | 
			
		||||
            outer_wall_volumetric_speed = outer_wall_speed * outer_wall_flow.mm3_per_mm();
 | 
			
		||||
            if (outer_wall_volumetric_speed > filament_max_volumetric_speed)
 | 
			
		||||
| 
						 | 
				
			
			@ -1637,7 +1833,17 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
 | 
			
		|||
    std::function<void(void)> throw_if_canceled_func = [&print]() { print.throw_if_canceled(); };
 | 
			
		||||
    m_seam_placer.init(print, throw_if_canceled_func);
 | 
			
		||||
 | 
			
		||||
    // BBS: priming logic is removed, always set first extruder here.
 | 
			
		||||
    // BBS: get path for change filament
 | 
			
		||||
    if (m_writer.multiple_extruders) {
 | 
			
		||||
        std::vector<Vec2d> points = get_path_of_change_filament(print);
 | 
			
		||||
        if (points.size() == 3) {
 | 
			
		||||
            travel_point_1 = points[0];
 | 
			
		||||
            travel_point_2 = points[1];
 | 
			
		||||
            travel_point_3 = points[2];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // BBS: priming logic is removed, always set first extruer here.
 | 
			
		||||
    //if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming))
 | 
			
		||||
    {
 | 
			
		||||
        // Set initial extruder only after custom start G-code.
 | 
			
		||||
| 
						 | 
				
			
			@ -1676,8 +1882,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
 | 
			
		|||
        gcode += pa_test.generate_test(params.start, params.step, std::llround(std::ceil((params.end - params.start) / params.step)));
 | 
			
		||||
 | 
			
		||||
        file.write(gcode);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
    } else {
 | 
			
		||||
        //BBS: open spaghetti detector
 | 
			
		||||
        if (is_bbl_printers) {
 | 
			
		||||
            // if (print.config().spaghetti_detector.value)
 | 
			
		||||
| 
						 | 
				
			
			@ -1685,11 +1890,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        // Do all objects for each layer.
 | 
			
		||||
        if (print.config().print_sequence == PrintSequence::ByObject) {
 | 
			
		||||
        if (print.config().print_sequence == PrintSequence::ByObject && !has_wipe_tower) {
 | 
			
		||||
            size_t finished_objects = 0;
 | 
			
		||||
            const PrintObject* prev_object = (*print_object_instance_sequential_active)->print_object;
 | 
			
		||||
            for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++print_object_instance_sequential_active) {
 | 
			
		||||
                const PrintObject& object = *(*print_object_instance_sequential_active)->print_object;
 | 
			
		||||
            const PrintObject *prev_object = (*print_object_instance_sequential_active)->print_object;
 | 
			
		||||
            for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++ print_object_instance_sequential_active) {
 | 
			
		||||
                const PrintObject &object = *(*print_object_instance_sequential_active)->print_object;
 | 
			
		||||
                if (&object != prev_object || tool_ordering.first_extruder() != final_extruder_id) {
 | 
			
		||||
                    tool_ordering = ToolOrdering(object, final_extruder_id);
 | 
			
		||||
                    unsigned int new_extruder_id = tool_ordering.first_extruder();
 | 
			
		||||
| 
						 | 
				
			
			@ -1697,7 +1902,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
 | 
			
		|||
                        // Skip this object.
 | 
			
		||||
                        continue;
 | 
			
		||||
                    initial_extruder_id = new_extruder_id;
 | 
			
		||||
                    final_extruder_id = tool_ordering.last_extruder();
 | 
			
		||||
                    final_extruder_id   = tool_ordering.last_extruder();
 | 
			
		||||
                    assert(final_extruder_id != (unsigned int)-1);
 | 
			
		||||
                }
 | 
			
		||||
                print.throw_if_canceled();
 | 
			
		||||
| 
						 | 
				
			
			@ -1751,34 +1956,33 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
 | 
			
		|||
                        file.write("M1003 S0\n");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
#ifdef HAS_PRESSURE_EQUALIZER
 | 
			
		||||
    #ifdef HAS_PRESSURE_EQUALIZER
 | 
			
		||||
                if (m_pressure_equalizer)
 | 
			
		||||
                    file.write(m_pressure_equalizer->process("", true));
 | 
			
		||||
#endif /* HAS_PRESSURE_EQUALIZER */
 | 
			
		||||
                ++finished_objects;
 | 
			
		||||
    #endif /* HAS_PRESSURE_EQUALIZER */
 | 
			
		||||
                ++ finished_objects;
 | 
			
		||||
                // Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed.
 | 
			
		||||
                // Reset it when starting another object from 1st layer.
 | 
			
		||||
                m_second_layer_things_done = false;
 | 
			
		||||
                prev_object = &object;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
        } else {
 | 
			
		||||
            // Sort layers by Z.
 | 
			
		||||
            // All extrusion moves with the same top layer height are extruded uninterrupted.
 | 
			
		||||
            std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print = collect_layers_to_print(print);
 | 
			
		||||
            // Prusa Multi-Material wipe tower.
 | 
			
		||||
            if (has_wipe_tower && !layers_to_print.empty()) {
 | 
			
		||||
                m_wipe_tower.reset(new WipeTowerIntegration(print.config(), print.get_plate_index(), print.get_plate_origin(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get()));
 | 
			
		||||
            if (has_wipe_tower && ! layers_to_print.empty()) {
 | 
			
		||||
                m_wipe_tower.reset(new WipeTowerIntegration(print.config(), print.get_plate_index(), print.get_plate_origin(), * print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get()));
 | 
			
		||||
                //BBS
 | 
			
		||||
                //file.write(m_writer.travel_to_z(initial_layer_print_height + m_config.z_offset.value, "Move to the first layer height"));
 | 
			
		||||
                file.write(m_writer.travel_to_z(initial_layer_print_height, "Move to the first layer height"));
 | 
			
		||||
#if 0
 | 
			
		||||
    #if 0
 | 
			
		||||
                if (print.config().single_extruder_multi_material_priming) {
 | 
			
		||||
                    file.write(m_wipe_tower->prime(*this));
 | 
			
		||||
                    // Verify, whether the print overaps the priming extrusions.
 | 
			
		||||
                    BoundingBoxf bbox_print(get_print_extrusions_extents(print));
 | 
			
		||||
                    coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON;
 | 
			
		||||
                    for (const PrintObject* print_object : print.objects())
 | 
			
		||||
                    for (const PrintObject *print_object : print.objects())
 | 
			
		||||
                        bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz));
 | 
			
		||||
                    bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz));
 | 
			
		||||
                    BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print));
 | 
			
		||||
| 
						 | 
				
			
			@ -1791,8 +1995,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
 | 
			
		|||
                        if (overlap) {
 | 
			
		||||
                            // Wait for the user to remove the priming extrusions.
 | 
			
		||||
                            file.write("M1 Remove priming towers and click button.\n");
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                        } else {
 | 
			
		||||
                            // Just wait for a bit to let the user check, that the priming succeeded.
 | 
			
		||||
                            //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix.
 | 
			
		||||
                            file.write("M1 S10\n");
 | 
			
		||||
| 
						 | 
				
			
			@ -1810,7 +2013,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
 | 
			
		|||
                        //}
 | 
			
		||||
                    //}
 | 
			
		||||
                }
 | 
			
		||||
#endif
 | 
			
		||||
    #endif
 | 
			
		||||
                print.throw_if_canceled();
 | 
			
		||||
            }
 | 
			
		||||
            // Process all layers of all objects (non-sequential mode) with a parallel pipeline:
 | 
			
		||||
| 
						 | 
				
			
			@ -1824,10 +2027,10 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
 | 
			
		|||
                    file.write("M1003 S0\n");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
#ifdef HAS_PRESSURE_EQUALIZER
 | 
			
		||||
    #ifdef HAS_PRESSURE_EQUALIZER
 | 
			
		||||
            if (m_pressure_equalizer)
 | 
			
		||||
                file.write(m_pressure_equalizer->process("", true));
 | 
			
		||||
#endif /* HAS_PRESSURE_EQUALIZER */
 | 
			
		||||
    #endif /* HAS_PRESSURE_EQUALIZER */
 | 
			
		||||
            if (m_wipe_tower)
 | 
			
		||||
                // Purge the extruder, pull out the active filament.
 | 
			
		||||
                file.write(m_wipe_tower->finalize(*this));
 | 
			
		||||
| 
						 | 
				
			
			@ -2502,7 +2705,6 @@ GCode::LayerResult GCode::process_layer(
 | 
			
		|||
    // First object, support and raft layer, if available.
 | 
			
		||||
    const Layer         *object_layer  = nullptr;
 | 
			
		||||
    const SupportLayer  *support_layer = nullptr;
 | 
			
		||||
    const TreeSupportLayer* tree_support_layer = nullptr;
 | 
			
		||||
    const SupportLayer  *raft_layer    = nullptr;
 | 
			
		||||
    for (const LayerToPrint &l : layers) {
 | 
			
		||||
        if (l.object_layer && ! object_layer)
 | 
			
		||||
| 
						 | 
				
			
			@ -2513,16 +2715,6 @@ GCode::LayerResult GCode::process_layer(
 | 
			
		|||
            if (! raft_layer && support_layer->id() < support_layer->object()->slicing_parameters().raft_layers())
 | 
			
		||||
                raft_layer = support_layer;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (l.tree_support_layer) {
 | 
			
		||||
            if (!tree_support_layer)
 | 
			
		||||
                tree_support_layer = l.tree_support_layer;
 | 
			
		||||
            // BBS: to be checked.
 | 
			
		||||
#if 0
 | 
			
		||||
            if (!raft_layer && tree_support_layer->id() < tree_support_layer->object()->slicing_parameters().raft_layers())
 | 
			
		||||
                raft_layer = tree_support_layer;
 | 
			
		||||
#endif
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const Layer* layer_ptr = nullptr;
 | 
			
		||||
| 
						 | 
				
			
			@ -2530,8 +2722,6 @@ GCode::LayerResult GCode::process_layer(
 | 
			
		|||
        layer_ptr = object_layer;
 | 
			
		||||
    else if (support_layer != nullptr)
 | 
			
		||||
        layer_ptr = support_layer;
 | 
			
		||||
    else
 | 
			
		||||
        layer_ptr = tree_support_layer;
 | 
			
		||||
    const Layer& layer = *layer_ptr;
 | 
			
		||||
    GCode::LayerResult   result { {}, layer.id(), false, last_layer };
 | 
			
		||||
    if (layer_tools.extruders.empty())
 | 
			
		||||
| 
						 | 
				
			
			@ -2552,7 +2742,7 @@ GCode::LayerResult GCode::process_layer(
 | 
			
		|||
    // Check whether it is possible to apply the spiral vase logic for this layer.
 | 
			
		||||
    // Just a reminder: A spiral vase mode is allowed for a single object, single material print only.
 | 
			
		||||
    m_enable_loop_clipping = true;
 | 
			
		||||
    if (m_spiral_vase && layers.size() == 1 && support_layer == nullptr && tree_support_layer == nullptr) {
 | 
			
		||||
    if (m_spiral_vase && layers.size() == 1 && support_layer == nullptr) {
 | 
			
		||||
        bool enable = (layer.id() > 0 || !print.has_brim()) && (layer.id() >= (size_t)print.config().skirt_height.value && ! print.has_infinite_skirt());
 | 
			
		||||
        if (enable) {
 | 
			
		||||
            for (const LayerRegion *layer_region : layer.regions())
 | 
			
		||||
| 
						 | 
				
			
			@ -2790,90 +2980,6 @@ GCode::LayerResult GCode::process_layer(
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // BBS
 | 
			
		||||
        if (layer_to_print.tree_support_layer != nullptr) {
 | 
			
		||||
            const TreeSupportLayer& tree_support_layer = *layer_to_print.tree_support_layer;
 | 
			
		||||
            const PrintObject& object = *layer_to_print.original_object;
 | 
			
		||||
            if (!tree_support_layer.support_fills.entities.empty()) {
 | 
			
		||||
                ExtrusionRole   role = tree_support_layer.support_fills.role();
 | 
			
		||||
                bool            has_support = role == erMixed || role == erSupportMaterial || role == erSupportTransition;
 | 
			
		||||
                bool            has_interface = role == erMixed || role == erSupportMaterialInterface;
 | 
			
		||||
                // Extruder ID of the support base. -1 if "don't care".
 | 
			
		||||
                unsigned int    support_extruder = object.config().support_filament.value - 1;
 | 
			
		||||
                // Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes?
 | 
			
		||||
                bool            support_dontcare = object.config().support_filament.value == 0;
 | 
			
		||||
                // Extruder ID of the support interface. -1 if "don't care".
 | 
			
		||||
                unsigned int    interface_extruder = object.config().support_interface_filament.value - 1;
 | 
			
		||||
                // Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes?
 | 
			
		||||
                bool            interface_dontcare = object.config().support_interface_filament.value == 0;
 | 
			
		||||
 | 
			
		||||
                // BBS: apply wiping overridden extruders
 | 
			
		||||
                WipingExtrusions& wiping_extrusions = const_cast<LayerTools&>(layer_tools).wiping_extrusions();
 | 
			
		||||
                if (support_dontcare) {
 | 
			
		||||
                    int extruder_override = wiping_extrusions.get_support_extruder_overrides(&object);
 | 
			
		||||
                    if (extruder_override >= 0) {
 | 
			
		||||
                        support_extruder = extruder_override;
 | 
			
		||||
                        support_dontcare = false;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (interface_dontcare) {
 | 
			
		||||
                    int extruder_override = wiping_extrusions.get_support_interface_extruder_overrides(&object);
 | 
			
		||||
                    if (extruder_override >= 0) {
 | 
			
		||||
                        interface_extruder = extruder_override;
 | 
			
		||||
                        interface_dontcare = false;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // BBS: try to print support base with a filament other than interface filament
 | 
			
		||||
                if (support_dontcare && !interface_dontcare) {
 | 
			
		||||
                    unsigned int dontcare_extruder = first_extruder_id;
 | 
			
		||||
                    for (unsigned int extruder_id : layer_tools.extruders) {
 | 
			
		||||
                        if (print.config().filament_soluble.get_at(extruder_id)) continue;
 | 
			
		||||
 | 
			
		||||
                        if (extruder_id == interface_extruder) continue;
 | 
			
		||||
 | 
			
		||||
                        dontcare_extruder = extruder_id;
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (support_dontcare) support_extruder = dontcare_extruder;
 | 
			
		||||
                }
 | 
			
		||||
                else if (support_dontcare || interface_dontcare) {
 | 
			
		||||
                    // Some support will be printed with "don't care" material, preferably non-soluble.
 | 
			
		||||
                    // Is the current extruder assigned a soluble filament?
 | 
			
		||||
                    unsigned int dontcare_extruder = first_extruder_id;
 | 
			
		||||
                    if (print.config().filament_soluble.get_at(dontcare_extruder)) {
 | 
			
		||||
                        // The last extruder printed on the previous layer extrudes soluble filament.
 | 
			
		||||
                        // Try to find a non-soluble extruder on the same layer.
 | 
			
		||||
                        for (unsigned int extruder_id : layer_tools.extruders)
 | 
			
		||||
                            if (!print.config().filament_soluble.get_at(extruder_id)) {
 | 
			
		||||
                                dontcare_extruder = extruder_id;
 | 
			
		||||
                                break;
 | 
			
		||||
                            }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (support_dontcare)
 | 
			
		||||
                        support_extruder = dontcare_extruder;
 | 
			
		||||
                    if (interface_dontcare)
 | 
			
		||||
                        interface_extruder = dontcare_extruder;
 | 
			
		||||
                }
 | 
			
		||||
                // Both the support and the support interface are printed with the same extruder, therefore
 | 
			
		||||
                // the interface may be interleaved with the support base.
 | 
			
		||||
                bool single_extruder = !has_support || support_extruder == interface_extruder;
 | 
			
		||||
 | 
			
		||||
                // Assign an extruder to the base.
 | 
			
		||||
                ObjectByExtruder& obj = object_by_extruder(by_extruder, has_support ? support_extruder : interface_extruder, &layer_to_print - layers.data(), layers.size());
 | 
			
		||||
                obj.support = &tree_support_layer.support_fills;
 | 
			
		||||
                obj.support_extrusion_role = single_extruder ? erMixed : erSupportMaterial;
 | 
			
		||||
                if (!single_extruder && has_interface) {
 | 
			
		||||
                    ObjectByExtruder& obj_interface = object_by_extruder(by_extruder, interface_extruder, &layer_to_print - layers.data(), layers.size());
 | 
			
		||||
                    obj_interface.support = &tree_support_layer.support_fills;
 | 
			
		||||
                    obj_interface.support_extrusion_role = erSupportMaterialInterface;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (layer_to_print.object_layer != nullptr) {
 | 
			
		||||
            const Layer &layer = *layer_to_print.object_layer;
 | 
			
		||||
            // We now define a strategy for building perimeters and fills. The separation
 | 
			
		||||
| 
						 | 
				
			
			@ -3024,8 +3130,9 @@ GCode::LayerResult GCode::process_layer(
 | 
			
		|||
        // BBS: ordering instances by extruder
 | 
			
		||||
        std::vector<InstanceToPrint> instances_to_print;
 | 
			
		||||
        bool has_prime_tower = print.config().enable_prime_tower
 | 
			
		||||
            && print.config().print_sequence == PrintSequence::ByLayer
 | 
			
		||||
            && print.extruders().size() > 1;
 | 
			
		||||
            && print.extruders().size() > 1
 | 
			
		||||
            && (print.config().print_sequence == PrintSequence::ByLayer
 | 
			
		||||
                || (print.config().print_sequence == PrintSequence::ByObject && print.objects().size() == 1));
 | 
			
		||||
        if (has_prime_tower) {
 | 
			
		||||
            int plate_idx = print.get_plate_index();
 | 
			
		||||
            Point wt_pos(print.config().wipe_tower_x.get_at(plate_idx), print.config().wipe_tower_y.get_at(plate_idx));
 | 
			
		||||
| 
						 | 
				
			
			@ -3097,12 +3204,7 @@ GCode::LayerResult GCode::process_layer(
 | 
			
		|||
                m_last_obj_copy = this_object_copy;
 | 
			
		||||
                this->set_origin(unscale(offset));
 | 
			
		||||
                if (instance_to_print.object_by_extruder.support != nullptr) {
 | 
			
		||||
                    if (layers[instance_to_print.layer_id].support_layer) {
 | 
			
		||||
                        m_layer = layers[instance_to_print.layer_id].support_layer;
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        m_layer = layers[instance_to_print.layer_id].tree_support_layer;
 | 
			
		||||
                    }
 | 
			
		||||
                    m_layer = layers[instance_to_print.layer_id].support_layer;
 | 
			
		||||
                    m_object_layer_over_raft = false;
 | 
			
		||||
 | 
			
		||||
                    //BBS: print supports' brims first
 | 
			
		||||
| 
						 | 
				
			
			@ -3303,8 +3405,11 @@ std::string GCode::change_layer(coordf_t print_z, bool lazy_raise)
 | 
			
		|||
    //BBS
 | 
			
		||||
    //coordf_t z = print_z + m_config.z_offset.value;  // in unscaled coordinates
 | 
			
		||||
    coordf_t z = print_z;  // in unscaled coordinates
 | 
			
		||||
    if (EXTRUDER_CONFIG(retract_when_changing_layer) && m_writer.will_move_z(z))
 | 
			
		||||
        gcode += this->retract();
 | 
			
		||||
    if (EXTRUDER_CONFIG(retract_when_changing_layer) && m_writer.will_move_z(z)) {
 | 
			
		||||
        LiftType lift_type = this->to_lift_type(ZHopType(EXTRUDER_CONFIG(z_hop_types)));
 | 
			
		||||
        //BBS: force to use SpiralLift when change layer if lift type is auto
 | 
			
		||||
        gcode += this->retract(false, false, ZHopType(EXTRUDER_CONFIG(z_hop_types)) == ZHopType::zhtAuto ? LiftType::SpiralLift : lift_type);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!lazy_raise) {
 | 
			
		||||
        std::ostringstream comment;
 | 
			
		||||
| 
						 | 
				
			
			@ -3421,6 +3526,12 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
 | 
			
		|||
        if (was_clockwise) {
 | 
			
		||||
            // swap points
 | 
			
		||||
            Point c = a; a = b; b = c;
 | 
			
		||||
 | 
			
		||||
    //    double angle = paths.front().first_point().ccw_angle(a, b) / 3;
 | 
			
		||||
 | 
			
		||||
    //    // turn left if contour, turn right if hole
 | 
			
		||||
    //    if (was_clockwise) angle *= -1;
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        double angle = paths.front().first_point().ccw_angle(a, b) / 3;
 | 
			
		||||
| 
						 | 
				
			
			@ -4067,7 +4178,8 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string
 | 
			
		|||
    Polyline travel { this->last_pos(), point };
 | 
			
		||||
 | 
			
		||||
    // check whether a straight travel move would need retraction
 | 
			
		||||
    bool needs_retraction             = this->needs_retraction(travel, role);
 | 
			
		||||
    LiftType lift_type = LiftType::SpiralLift;
 | 
			
		||||
    bool needs_retraction = this->needs_retraction(travel, role, lift_type);
 | 
			
		||||
    // check whether wipe could be disabled without causing visible stringing
 | 
			
		||||
    bool could_be_wipe_disabled       = false;
 | 
			
		||||
    // Save state of use_external_mp_once for the case that will be needed to call twice m_avoid_crossing_perimeters.travel_to.
 | 
			
		||||
| 
						 | 
				
			
			@ -4104,10 +4216,12 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string
 | 
			
		|||
    // multi-hop travel path inside the configuration space
 | 
			
		||||
    if (needs_retraction
 | 
			
		||||
        && m_config.reduce_crossing_wall
 | 
			
		||||
        && ! m_avoid_crossing_perimeters.disabled_once()) {
 | 
			
		||||
        && ! m_avoid_crossing_perimeters.disabled_once()
 | 
			
		||||
        //BBS: don't generate detour travel paths when current position is unclear
 | 
			
		||||
        && m_writer.is_current_position_clear()) {
 | 
			
		||||
        travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled);
 | 
			
		||||
        // check again whether the new travel path still needs a retraction
 | 
			
		||||
        needs_retraction = this->needs_retraction(travel, role);
 | 
			
		||||
        needs_retraction = this->needs_retraction(travel, role, lift_type);
 | 
			
		||||
        //if (needs_retraction && m_layer_index > 1) exit(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -4120,7 +4234,7 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string
 | 
			
		|||
            m_wipe.reset_path();
 | 
			
		||||
 | 
			
		||||
        Point last_post_before_retract = this->last_pos();
 | 
			
		||||
        gcode += this->retract();
 | 
			
		||||
        gcode += this->retract(false, false, lift_type);
 | 
			
		||||
        // When "Wipe while retracting" is enabled, then extruder moves to another position, and travel from this position can cross perimeters.
 | 
			
		||||
        // Because of it, it is necessary to call avoid crossing perimeters again with new starting point after calling retraction()
 | 
			
		||||
        // FIXME Lukas H.: Try to predict if this second calling of avoid crossing perimeters will be needed or not. It could save computations.
 | 
			
		||||
| 
						 | 
				
			
			@ -4155,36 +4269,102 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string
 | 
			
		|||
    return gcode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role)
 | 
			
		||||
//BBS
 | 
			
		||||
LiftType GCode::to_lift_type(ZHopType z_hop_types) {
 | 
			
		||||
    switch (z_hop_types)
 | 
			
		||||
    {
 | 
			
		||||
    case ZHopType::zhtNormal:
 | 
			
		||||
        return LiftType::NormalLift;
 | 
			
		||||
    case ZHopType::zhtSlope:
 | 
			
		||||
        return LiftType::LazyLift;
 | 
			
		||||
    case ZHopType::zhtSpiral:
 | 
			
		||||
        return LiftType::SpiralLift;
 | 
			
		||||
    default:
 | 
			
		||||
        // if no corresponding lift type, use normal lift
 | 
			
		||||
        return LiftType::NormalLift;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role, LiftType& lift_type)
 | 
			
		||||
{
 | 
			
		||||
    if (travel.length() < scale_(EXTRUDER_CONFIG(retraction_minimum_travel))) {
 | 
			
		||||
        // skip retraction if the move is shorter than the configured threshold
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto is_through_overhang = [this](const Polyline& travel) {
 | 
			
		||||
        const float protect_z_scaled = scale_(0.4);
 | 
			
		||||
        std::pair<float, float> z_range;
 | 
			
		||||
        z_range.second = m_layer ? m_layer->print_z : 0.f;
 | 
			
		||||
        z_range.first = std::max(0.f, z_range.second - protect_z_scaled);
 | 
			
		||||
        for (auto object : m_curr_print->objects()) {
 | 
			
		||||
            BoundingBox obj_bbox = object->bounding_box();
 | 
			
		||||
            BoundingBox travel_bbox = get_extents(travel);
 | 
			
		||||
            obj_bbox.offset(scale_(EPSILON));
 | 
			
		||||
            if (!obj_bbox.overlap(travel_bbox))
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            for (auto layer : object->layers()) {
 | 
			
		||||
                if (layer->print_z < z_range.first)
 | 
			
		||||
                    continue;
 | 
			
		||||
 | 
			
		||||
                if (layer->print_z > z_range.second + EPSILON)
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                for (ExPolygon& overhang : layer->loverhangs) {
 | 
			
		||||
                    if (overhang.contains(travel))
 | 
			
		||||
                        return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    float max_z_hop = 0.f;
 | 
			
		||||
    for (int i = 0; i < m_config.z_hop.size(); i++)
 | 
			
		||||
        max_z_hop = std::max(max_z_hop, (float)m_config.z_hop.get_at(i));
 | 
			
		||||
    float travel_len_thresh = max_z_hop / tan(GCodeWriter::slope_threshold);
 | 
			
		||||
    float accum_len = 0.f;
 | 
			
		||||
    Polyline clipped_travel;
 | 
			
		||||
    for (auto line : travel.lines()) {
 | 
			
		||||
        if (accum_len + line.length() > travel_len_thresh + EPSILON) {
 | 
			
		||||
            Point end_pnt = line.a + line.normal() * (travel_len_thresh - accum_len);
 | 
			
		||||
            clipped_travel.append(Polyline(line.a, end_pnt));
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            clipped_travel.append(Polyline(line.a, line.b));
 | 
			
		||||
            accum_len += line.length();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //BBS: force to retract when leave from external perimeter for a long travel
 | 
			
		||||
    //Better way is judging whether the travel move direction is same with last extrusion move.
 | 
			
		||||
    if (is_perimeter(m_last_processor_extrusion_role) && m_last_processor_extrusion_role != erPerimeter)
 | 
			
		||||
    if (is_perimeter(m_last_processor_extrusion_role) && m_last_processor_extrusion_role != erPerimeter) {
 | 
			
		||||
        if (ZHopType(EXTRUDER_CONFIG(z_hop_types)) == ZHopType::zhtAuto) {
 | 
			
		||||
            lift_type = is_through_overhang(clipped_travel) ? LiftType::SpiralLift : LiftType::LazyLift;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            lift_type = to_lift_type(ZHopType(EXTRUDER_CONFIG(z_hop_types)));
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (role == erSupportMaterial || role == erSupportTransition) {
 | 
			
		||||
        const SupportLayer* support_layer = dynamic_cast<const SupportLayer*>(m_layer);
 | 
			
		||||
 | 
			
		||||
        //FIXME support_layer->support_islands.contains should use some search structure!
 | 
			
		||||
        if (support_layer != NULL && support_layer->support_islands.contains(travel))
 | 
			
		||||
            // skip retraction if this is a travel move inside a support material island
 | 
			
		||||
            //FIXME not retracting over a long path may cause oozing, which in turn may result in missing material
 | 
			
		||||
            // at the end of the extrusion path!
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        //reduce the retractions in lightning infills for tree support
 | 
			
		||||
        const TreeSupportLayer* ts_layer = dynamic_cast<const TreeSupportLayer*>(m_layer);
 | 
			
		||||
        if (ts_layer != NULL)
 | 
			
		||||
            for (auto& area : ts_layer->base_areas)
 | 
			
		||||
                if(area.contains(travel))
 | 
			
		||||
        if (support_layer != NULL && support_layer->support_type==stInnerTree)
 | 
			
		||||
            for (auto &area : support_layer->base_areas)
 | 
			
		||||
                if (area.contains(travel))
 | 
			
		||||
                    return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //BBS: need retract when long moving to print perimeter to avoid dropping of material
 | 
			
		||||
    if (!is_perimeter(role) && m_config.reduce_infill_retraction && m_layer != nullptr &&
 | 
			
		||||
        m_config.sparse_infill_density.value > 0 && m_layer->any_internal_region_slice_contains(travel))
 | 
			
		||||
| 
						 | 
				
			
			@ -4194,10 +4374,16 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role)
 | 
			
		|||
        return false;
 | 
			
		||||
 | 
			
		||||
    // retract if reduce_infill_retraction is disabled or doesn't apply when role is perimeter
 | 
			
		||||
    if (ZHopType(EXTRUDER_CONFIG(z_hop_types)) == ZHopType::zhtAuto) {
 | 
			
		||||
        lift_type = is_through_overhang(clipped_travel) ? LiftType::SpiralLift : LiftType::LazyLift;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        lift_type = to_lift_type(ZHopType(EXTRUDER_CONFIG(z_hop_types)));
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string GCode::retract(bool toolchange, bool is_last_retraction)
 | 
			
		||||
std::string GCode::retract(bool toolchange, bool is_last_retraction, LiftType lift_type)
 | 
			
		||||
{
 | 
			
		||||
    std::string gcode;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -4221,11 +4407,7 @@ std::string GCode::retract(bool toolchange, bool is_last_retraction)
 | 
			
		|||
    if (m_writer.extruder()->retraction_length() > 0 || m_config.use_firmware_retraction) {
 | 
			
		||||
        // BBS: don't do lazy_lift when enable spiral vase
 | 
			
		||||
        size_t extruder_id = m_writer.extruder()->id();
 | 
			
		||||
        auto _lift = m_config.z_lift_type.value;
 | 
			
		||||
        if(m_spiral_vase)
 | 
			
		||||
            _lift = NormalLift;
 | 
			
		||||
 | 
			
		||||
        gcode += m_writer.lift(_lift);
 | 
			
		||||
        gcode += m_writer.lift(!m_spiral_vase ? lift_type : LiftType::NormalLift);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return gcode;
 | 
			
		||||
| 
						 | 
				
			
			@ -4340,6 +4522,12 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
 | 
			
		|||
    dyn_config.set_key_value("second_flush_volume", new ConfigOptionFloat(wipe_length / 2.f));
 | 
			
		||||
    dyn_config.set_key_value("old_filament_e_feedrate", new ConfigOptionInt(old_filament_e_feedrate));
 | 
			
		||||
    dyn_config.set_key_value("new_filament_e_feedrate", new ConfigOptionInt(new_filament_e_feedrate));
 | 
			
		||||
    dyn_config.set_key_value("travel_point_1_x", new ConfigOptionFloat(float(travel_point_1.x())));
 | 
			
		||||
    dyn_config.set_key_value("travel_point_1_y", new ConfigOptionFloat(float(travel_point_1.y())));
 | 
			
		||||
    dyn_config.set_key_value("travel_point_2_x", new ConfigOptionFloat(float(travel_point_2.x())));
 | 
			
		||||
    dyn_config.set_key_value("travel_point_2_y", new ConfigOptionFloat(float(travel_point_2.y())));
 | 
			
		||||
    dyn_config.set_key_value("travel_point_3_x", new ConfigOptionFloat(float(travel_point_3.x())));
 | 
			
		||||
    dyn_config.set_key_value("travel_point_3_y", new ConfigOptionFloat(float(travel_point_3.y())));
 | 
			
		||||
 | 
			
		||||
    int flush_count = std::min(g_max_flush_count, (int)std::round(wipe_volume / g_purge_volume_one_time));
 | 
			
		||||
    float flush_unit = wipe_length / flush_count;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -197,8 +197,8 @@ public:
 | 
			
		|||
    void            apply_print_config(const PrintConfig &print_config);
 | 
			
		||||
 | 
			
		||||
    std::string     travel_to(const Point& point, ExtrusionRole role, std::string comment);
 | 
			
		||||
    bool            needs_retraction(const Polyline& travel, ExtrusionRole role = erNone);
 | 
			
		||||
    std::string     retract(bool toolchange = false, bool is_last_retraction = false);
 | 
			
		||||
    bool            needs_retraction(const Polyline& travel, ExtrusionRole role, LiftType& lift_type);
 | 
			
		||||
    std::string     retract(bool toolchange = false, bool is_last_retraction = false, LiftType lift_type = LiftType::SpiralLift);
 | 
			
		||||
    std::string     unretract() { return m_writer.unlift() + m_writer.unretract(); }
 | 
			
		||||
    std::string     set_extruder(unsigned int extruder_id, double print_z);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -209,10 +209,9 @@ public:
 | 
			
		|||
    // public, so that it could be accessed by free helper functions from GCode.cpp
 | 
			
		||||
    struct LayerToPrint
 | 
			
		||||
    {
 | 
			
		||||
        LayerToPrint() : object_layer(nullptr), support_layer(nullptr), tree_support_layer(nullptr), original_object(nullptr) {}
 | 
			
		||||
        LayerToPrint() : object_layer(nullptr), support_layer(nullptr), original_object(nullptr) {}
 | 
			
		||||
        const Layer* 		object_layer;
 | 
			
		||||
        const SupportLayer* support_layer;
 | 
			
		||||
        const TreeSupportLayer* tree_support_layer;
 | 
			
		||||
        const PrintObject*  original_object; //BBS: used for shared object logic
 | 
			
		||||
        const Layer* 		layer()   const
 | 
			
		||||
        {
 | 
			
		||||
| 
						 | 
				
			
			@ -222,9 +221,6 @@ public:
 | 
			
		|||
            if (support_layer != nullptr)
 | 
			
		||||
                return support_layer;
 | 
			
		||||
 | 
			
		||||
            if (tree_support_layer != nullptr)
 | 
			
		||||
                return tree_support_layer;
 | 
			
		||||
 | 
			
		||||
            return nullptr;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -246,10 +242,6 @@ public:
 | 
			
		|||
                count++;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (tree_support_layer != nullptr) {
 | 
			
		||||
                sum_z += tree_support_layer->print_z;
 | 
			
		||||
                count++;
 | 
			
		||||
            }
 | 
			
		||||
            return sum_z / count;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			@ -409,6 +401,10 @@ private:
 | 
			
		|||
    std::string     extrude_perimeters(const Print& print, const std::vector<ObjectByExtruder::Island::Region>& by_region);
 | 
			
		||||
    std::string     extrude_infill(const Print& print, const std::vector<ObjectByExtruder::Island::Region>& by_region, bool ironing);
 | 
			
		||||
    std::string     extrude_support(const ExtrusionEntityCollection& support_fills);
 | 
			
		||||
 | 
			
		||||
    // BBS
 | 
			
		||||
    LiftType to_lift_type(ZHopType z_hop_types);
 | 
			
		||||
 | 
			
		||||
    std::set<ObjectID>              m_objsWithBrim; // indicates the objs with brim
 | 
			
		||||
    std::set<ObjectID>              m_objSupportsWithBrim; // indicates the objs' supports with brim
 | 
			
		||||
    // Cache for custom seam enforcers/blockers for each layer.
 | 
			
		||||
| 
						 | 
				
			
			@ -491,6 +487,7 @@ private:
 | 
			
		|||
    GCodeProcessor m_processor;
 | 
			
		||||
 | 
			
		||||
    // BBS
 | 
			
		||||
    Print* m_curr_print = nullptr;
 | 
			
		||||
    unsigned int m_toolchange_count;
 | 
			
		||||
    coordf_t m_nominal_z;
 | 
			
		||||
    bool m_need_change_layer_lift_z = false;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1135,8 +1135,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
 | 
			
		|||
 | 
			
		||||
    const ExPolygons               &lslices          = gcodegen.layer()->lslices;
 | 
			
		||||
    const std::vector<BoundingBox> &lslices_bboxes   = gcodegen.layer()->lslices_bboxes;
 | 
			
		||||
    bool                            is_support_layer = (dynamic_cast<const SupportLayer *>(gcodegen.layer()) != nullptr) ||
 | 
			
		||||
                                                       (dynamic_cast<const TreeSupportLayer *>(gcodegen.layer()) != nullptr);
 | 
			
		||||
    bool                            is_support_layer = (dynamic_cast<const SupportLayer *>(gcodegen.layer()) != nullptr);
 | 
			
		||||
    if (!use_external && (is_support_layer || (!lslices.empty() && !any_expolygon_contains(lslices, lslices_bboxes, m_grid_lslice, travel)))) {
 | 
			
		||||
        // Initialize m_internal only when it is necessary.
 | 
			
		||||
        if (m_internal.boundaries.empty())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -244,22 +244,22 @@ float new_feedrate_to_reach_time_stretch(
 | 
			
		|||
{
 | 
			
		||||
	float new_feedrate = min_feedrate;
 | 
			
		||||
    for (size_t iter = 0; iter < max_iter; ++ iter) {
 | 
			
		||||
        float nomin = 0;
 | 
			
		||||
        float denom = time_stretch;
 | 
			
		||||
        double nomin = 0;
 | 
			
		||||
        double denom = time_stretch;
 | 
			
		||||
        for (auto it = it_begin; it != it_end; ++ it) {
 | 
			
		||||
			assert((*it)->slow_down_min_speed < min_feedrate + EPSILON);
 | 
			
		||||
			for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) {
 | 
			
		||||
				const CoolingLine &line = (*it)->lines[i];
 | 
			
		||||
                if (line.feedrate > min_feedrate) {
 | 
			
		||||
                    nomin += line.time * line.feedrate;
 | 
			
		||||
                    denom += line.time;
 | 
			
		||||
                    nomin += (double)line.time * (double)line.feedrate;
 | 
			
		||||
                    denom += (double)line.time;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        assert(denom > 0);
 | 
			
		||||
        if (denom < 0)
 | 
			
		||||
            return min_feedrate;
 | 
			
		||||
        new_feedrate = nomin / denom;
 | 
			
		||||
        new_feedrate = (float)(nomin / denom);
 | 
			
		||||
        assert(new_feedrate > min_feedrate - EPSILON);
 | 
			
		||||
        if (new_feedrate < min_feedrate + EPSILON)
 | 
			
		||||
            goto finished;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -55,7 +55,8 @@ const std::vector<std::string> GCodeProcessor::Reserved_Tags = {
 | 
			
		|||
    " CUSTOM_GCODE",
 | 
			
		||||
    "_GP_FIRST_LINE_M73_PLACEHOLDER",
 | 
			
		||||
    "_GP_LAST_LINE_M73_PLACEHOLDER",
 | 
			
		||||
    "_GP_ESTIMATED_PRINTING_TIME_PLACEHOLDER"
 | 
			
		||||
    "_GP_ESTIMATED_PRINTING_TIME_PLACEHOLDER",
 | 
			
		||||
    "_GP_TOTAL_LAYER_NUMBER_PLACEHOLDER"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const std::vector<std::string> GCodeProcessor::Reserved_Tags_compatible = {
 | 
			
		||||
| 
						 | 
				
			
			@ -227,6 +228,7 @@ void GCodeProcessor::TimeMachine::reset()
 | 
			
		|||
    std::fill(moves_time.begin(), moves_time.end(), 0.0f);
 | 
			
		||||
    std::fill(roles_time.begin(), roles_time.end(), 0.0f);
 | 
			
		||||
    layers_time = std::vector<float>();
 | 
			
		||||
    prepare_time = 0.0f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GCodeProcessor::TimeMachine::simulate_st_synchronize(float additional_time)
 | 
			
		||||
| 
						 | 
				
			
			@ -334,7 +336,9 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks, floa
 | 
			
		|||
 | 
			
		||||
        time += block_time;
 | 
			
		||||
        gcode_time.cache += block_time;
 | 
			
		||||
        moves_time[static_cast<size_t>(block.move_type)] += block_time;
 | 
			
		||||
        //BBS: don't calculate travel of start gcode into travel time
 | 
			
		||||
        if (!block.flags.prepare_stage || block.move_type != EMoveType::Travel)
 | 
			
		||||
            moves_time[static_cast<size_t>(block.move_type)] += block_time;
 | 
			
		||||
        roles_time[static_cast<size_t>(block.role)] += block_time;
 | 
			
		||||
        if (block.layer_id >= layers_time.size()) {
 | 
			
		||||
            const size_t curr_size = layers_time.size();
 | 
			
		||||
| 
						 | 
				
			
			@ -344,6 +348,9 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks, floa
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
        layers_time[block.layer_id - 1] += block_time;
 | 
			
		||||
        //BBS
 | 
			
		||||
        if (block.flags.prepare_stage)
 | 
			
		||||
            prepare_time += block_time;
 | 
			
		||||
        g1_times_cache.push_back({ block.g1_line_id, time });
 | 
			
		||||
        // update times for remaining time to printer stop placeholders
 | 
			
		||||
        auto it_stop_time = std::lower_bound(stop_times.begin(), stop_times.end(), block.g1_line_id,
 | 
			
		||||
| 
						 | 
				
			
			@ -371,7 +378,7 @@ void GCodeProcessor::TimeProcessor::reset()
 | 
			
		|||
    machines[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].enabled = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, std::vector<GCodeProcessorResult::MoveVertex>& moves, std::vector<size_t>& lines_ends)
 | 
			
		||||
void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, std::vector<GCodeProcessorResult::MoveVertex>& moves, std::vector<size_t>& lines_ends, size_t total_layer_num)
 | 
			
		||||
{
 | 
			
		||||
    FilePtr in{ boost::nowide::fopen(filename.c_str(), "rb") };
 | 
			
		||||
    if (in.f == nullptr)
 | 
			
		||||
| 
						 | 
				
			
			@ -480,14 +487,20 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st
 | 
			
		|||
                        //sprintf(buf, "; estimated printing time (%s mode) = %s\n",
 | 
			
		||||
                        //    (mode == PrintEstimatedStatistics::ETimeMode::Normal) ? "normal" : "silent",
 | 
			
		||||
                        //    get_time_dhms(machine.time).c_str());
 | 
			
		||||
                            sprintf(buf, "; model printing time: %s; total estimated time: %s\n",
 | 
			
		||||
                                    get_time_dhms(machine.time - machine.roles_time[ExtrusionRole::erCustom]).c_str(),
 | 
			
		||||
                                    get_time_dhms(machine.time).c_str());
 | 
			
		||||
                        sprintf(buf, "; model printing time: %s; total estimated time: %s\n",
 | 
			
		||||
                                get_time_dhms(machine.time - machine.prepare_time).c_str(),
 | 
			
		||||
                                get_time_dhms(machine.time).c_str());
 | 
			
		||||
                        }
 | 
			
		||||
                        ret += buf;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            //BBS: write total layer number
 | 
			
		||||
            else if (line == reserved_tag(ETags::Total_Layer_Number_Placeholder)) {
 | 
			
		||||
                char buf[128];
 | 
			
		||||
                sprintf(buf, "; total layer number: %zd\n", total_layer_num);
 | 
			
		||||
                ret += buf;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (! ret.empty())
 | 
			
		||||
| 
						 | 
				
			
			@ -783,6 +796,7 @@ void GCodeProcessorResult::reset() {
 | 
			
		|||
    filament_diameters = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER);
 | 
			
		||||
    filament_densities = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY);
 | 
			
		||||
    custom_gcode_per_print_z = std::vector<CustomGCode::Item>();
 | 
			
		||||
    spiral_vase_layers = std::vector<std::pair<float, std::pair<size_t, size_t>>>();
 | 
			
		||||
    time = 0;
 | 
			
		||||
 | 
			
		||||
    //BBS: add mutex for protection of gcode result
 | 
			
		||||
| 
						 | 
				
			
			@ -808,6 +822,7 @@ void GCodeProcessorResult::reset() {
 | 
			
		|||
    required_nozzle_HRC = std::vector<int>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_HRC);
 | 
			
		||||
    filament_densities = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY);
 | 
			
		||||
    custom_gcode_per_print_z = std::vector<CustomGCode::Item>();
 | 
			
		||||
    spiral_vase_layers = std::vector<std::pair<float, std::pair<size_t, size_t>>>();
 | 
			
		||||
    warnings.clear();
 | 
			
		||||
 | 
			
		||||
    //BBS: add mutex for protection of gcode result
 | 
			
		||||
| 
						 | 
				
			
			@ -962,6 +977,10 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
 | 
			
		|||
        m_first_layer_height = std::abs(initial_layer_print_height->value);
 | 
			
		||||
 | 
			
		||||
    m_result.printable_height = config.printable_height;
 | 
			
		||||
 | 
			
		||||
    const ConfigOptionBool* spiral_vase = config.option<ConfigOptionBool>("spiral_mode");
 | 
			
		||||
    if (spiral_vase != nullptr)
 | 
			
		||||
        m_spiral_vase_active = spiral_vase->value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
 | 
			
		||||
| 
						 | 
				
			
			@ -1224,6 +1243,10 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
 | 
			
		|||
    const ConfigOptionFloat* printable_height = config.option<ConfigOptionFloat>("printable_height");
 | 
			
		||||
    if (printable_height != nullptr)
 | 
			
		||||
        m_result.printable_height = printable_height->value;
 | 
			
		||||
 | 
			
		||||
    const ConfigOptionBool* spiral_vase = config.option<ConfigOptionBool>("spiral_mode");
 | 
			
		||||
    if (spiral_vase != nullptr)
 | 
			
		||||
        m_spiral_vase_active = spiral_vase->value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GCodeProcessor::enable_stealth_time_estimator(bool enabled)
 | 
			
		||||
| 
						 | 
				
			
			@ -1294,6 +1317,8 @@ void GCodeProcessor::reset()
 | 
			
		|||
 | 
			
		||||
    m_options_z_corrector.reset();
 | 
			
		||||
 | 
			
		||||
    m_spiral_vase_active = false;
 | 
			
		||||
 | 
			
		||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
 | 
			
		||||
    m_mm3_per_mm_compare.reset();
 | 
			
		||||
    m_height_compare.reset();
 | 
			
		||||
| 
						 | 
				
			
			@ -1449,7 +1474,7 @@ void GCodeProcessor::finalize(bool post_process)
 | 
			
		|||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
 | 
			
		||||
 | 
			
		||||
    if (post_process)
 | 
			
		||||
        m_time_processor.post_process(m_result.filename, m_result.moves, m_result.lines_ends);
 | 
			
		||||
        m_time_processor.post_process(m_result.filename, m_result.moves, m_result.lines_ends, m_layer_id);
 | 
			
		||||
#if ENABLE_GCODE_VIEWER_STATISTICS
 | 
			
		||||
    m_result.time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - m_start_time).count();
 | 
			
		||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
 | 
			
		||||
| 
						 | 
				
			
			@ -1462,6 +1487,11 @@ float GCodeProcessor::get_time(PrintEstimatedStatistics::ETimeMode mode) const
 | 
			
		|||
    return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast<size_t>(mode)].time : 0.0f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float GCodeProcessor::get_prepare_time(PrintEstimatedStatistics::ETimeMode mode) const
 | 
			
		||||
{
 | 
			
		||||
    return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast<size_t>(mode)].prepare_time : 0.0f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string GCodeProcessor::get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const
 | 
			
		||||
{
 | 
			
		||||
    return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast<size_t>(mode)].time)) : std::string("N/A");
 | 
			
		||||
| 
						 | 
				
			
			@ -2044,6 +2074,18 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers
 | 
			
		|||
    // layer change tag
 | 
			
		||||
    if (comment == reserved_tag(ETags::Layer_Change)) {
 | 
			
		||||
        ++m_layer_id;
 | 
			
		||||
        if (m_spiral_vase_active) {
 | 
			
		||||
            if (m_result.moves.empty() || m_result.spiral_vase_layers.empty())
 | 
			
		||||
                // add a placeholder for layer height. the actual value will be set inside process_G1() method
 | 
			
		||||
                m_result.spiral_vase_layers.push_back({ FLT_MAX, { 0, 0 } });
 | 
			
		||||
            else {
 | 
			
		||||
                const size_t move_id = m_result.moves.size() - 1;
 | 
			
		||||
                if (!m_result.spiral_vase_layers.empty())
 | 
			
		||||
                    m_result.spiral_vase_layers.back().second.second = move_id;
 | 
			
		||||
                // add a placeholder for layer height. the actual value will be set inside process_G1() method
 | 
			
		||||
                m_result.spiral_vase_layers.push_back({ FLT_MAX, { move_id, move_id } });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2703,10 +2745,12 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
 | 
			
		|||
 | 
			
		||||
        TimeBlock block;
 | 
			
		||||
        block.move_type = type;
 | 
			
		||||
        block.role = m_extrusion_role;
 | 
			
		||||
        //BBS: don't calculate travel time into extrusion path, except travel inside start and end gcode.
 | 
			
		||||
        block.role = (type != EMoveType::Travel || m_extrusion_role == erCustom) ? m_extrusion_role : erNone;
 | 
			
		||||
        block.distance = distance;
 | 
			
		||||
        block.g1_line_id = m_g1_line_id;
 | 
			
		||||
        block.layer_id = std::max<unsigned int>(1, m_layer_id);
 | 
			
		||||
        block.flags.prepare_stage = m_processing_start_custom_gcode;
 | 
			
		||||
 | 
			
		||||
        //BBS: limite the cruise according to centripetal acceleration
 | 
			
		||||
        //Only need to handle when both prev and curr segment has movement in x-y plane
 | 
			
		||||
| 
						 | 
				
			
			@ -2924,6 +2968,14 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
 | 
			
		|||
        m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id] - plate_offset);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (m_spiral_vase_active && !m_result.spiral_vase_layers.empty()) {
 | 
			
		||||
        if (m_result.spiral_vase_layers.back().first == FLT_MAX && delta_pos[Z] > 0.0)
 | 
			
		||||
            // replace layer height placeholder with correct value
 | 
			
		||||
            m_result.spiral_vase_layers.back().first = static_cast<float>(m_end_position[Z]);
 | 
			
		||||
        if (!m_result.moves.empty())
 | 
			
		||||
            m_result.spiral_vase_layers.back().second.second = m_result.moves.size() - 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // store move
 | 
			
		||||
    store_move_vertex(type);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3125,10 +3177,12 @@ void  GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line)
 | 
			
		|||
 | 
			
		||||
        TimeBlock block;
 | 
			
		||||
        block.move_type = type;
 | 
			
		||||
        block.role = m_extrusion_role;
 | 
			
		||||
        //BBS: don't calculate travel time into extrusion path, except travel inside start and end gcode.
 | 
			
		||||
        block.role = (type != EMoveType::Travel || m_extrusion_role == erCustom) ? m_extrusion_role : erNone;
 | 
			
		||||
        block.distance = delta_xyz;
 | 
			
		||||
        block.g1_line_id = m_g1_line_id;
 | 
			
		||||
        block.layer_id = std::max<unsigned int>(1, m_layer_id);
 | 
			
		||||
        block.flags.prepare_stage = m_processing_start_custom_gcode;
 | 
			
		||||
 | 
			
		||||
        // BBS: calculates block cruise feedrate
 | 
			
		||||
        // For arc move, we need to limite the cruise according to centripetal acceleration which is
 | 
			
		||||
| 
						 | 
				
			
			@ -4103,6 +4157,7 @@ void GCodeProcessor::update_estimated_times_stats()
 | 
			
		|||
    auto update_mode = [this](PrintEstimatedStatistics::ETimeMode mode) {
 | 
			
		||||
        PrintEstimatedStatistics::Mode& data = m_result.print_statistics.modes[static_cast<size_t>(mode)];
 | 
			
		||||
        data.time = get_time(mode);
 | 
			
		||||
        data.prepare_time = get_prepare_time(mode);
 | 
			
		||||
        data.custom_gcode_times = get_custom_gcode_times(mode, true);
 | 
			
		||||
        data.moves_times = get_moves_time(mode);
 | 
			
		||||
        data.roles_times = get_roles_time(mode);
 | 
			
		||||
| 
						 | 
				
			
			@ -4176,4 +4231,3 @@ void GCodeProcessor::update_slice_warnings()
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
} /* namespace Slic3r */
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -49,6 +49,7 @@ namespace Slic3r {
 | 
			
		|||
        struct Mode
 | 
			
		||||
        {
 | 
			
		||||
            float time;
 | 
			
		||||
            float prepare_time;
 | 
			
		||||
            std::vector<std::pair<CustomGCode::Type, std::pair<float, float>>> custom_gcode_times;
 | 
			
		||||
            std::vector<std::pair<EMoveType, float>> moves_times;
 | 
			
		||||
            std::vector<std::pair<ExtrusionRole, float>> roles_times;
 | 
			
		||||
| 
						 | 
				
			
			@ -56,6 +57,7 @@ namespace Slic3r {
 | 
			
		|||
 | 
			
		||||
            void reset() {
 | 
			
		||||
                time = 0.0f;
 | 
			
		||||
                prepare_time = 0.0f;
 | 
			
		||||
                custom_gcode_times.clear();
 | 
			
		||||
                moves_times.clear();
 | 
			
		||||
                roles_times.clear();
 | 
			
		||||
| 
						 | 
				
			
			@ -163,6 +165,7 @@ namespace Slic3r {
 | 
			
		|||
        std::vector<int> filament_vitrification_temperature;
 | 
			
		||||
        PrintEstimatedStatistics print_statistics;
 | 
			
		||||
        std::vector<CustomGCode::Item> custom_gcode_per_print_z;
 | 
			
		||||
        std::vector<std::pair<float, std::pair<size_t, size_t>>> spiral_vase_layers;
 | 
			
		||||
        //BBS
 | 
			
		||||
        std::vector<SliceWarning> warnings;
 | 
			
		||||
        int nozzle_hrc;
 | 
			
		||||
| 
						 | 
				
			
			@ -191,6 +194,7 @@ namespace Slic3r {
 | 
			
		|||
            filament_densities = other.filament_densities;
 | 
			
		||||
            print_statistics = other.print_statistics;
 | 
			
		||||
            custom_gcode_per_print_z = other.custom_gcode_per_print_z;
 | 
			
		||||
            spiral_vase_layers = other.spiral_vase_layers;
 | 
			
		||||
            warnings = other.warnings;
 | 
			
		||||
#if ENABLE_GCODE_VIEWER_STATISTICS
 | 
			
		||||
            time = other.time;
 | 
			
		||||
| 
						 | 
				
			
			@ -223,7 +227,8 @@ namespace Slic3r {
 | 
			
		|||
            Custom_Code,
 | 
			
		||||
            First_Line_M73_Placeholder,
 | 
			
		||||
            Last_Line_M73_Placeholder,
 | 
			
		||||
            Estimated_Printing_Time_Placeholder
 | 
			
		||||
            Estimated_Printing_Time_Placeholder,
 | 
			
		||||
            Total_Layer_Number_Placeholder
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        static const std::string& reserved_tag(ETags tag) { return s_IsBBLPrinter ? Reserved_Tags[static_cast<unsigned char>(tag)] : Reserved_Tags_compatible[static_cast<unsigned char>(tag)]; }
 | 
			
		||||
| 
						 | 
				
			
			@ -301,6 +306,7 @@ namespace Slic3r {
 | 
			
		|||
            {
 | 
			
		||||
                bool recalculate{ false };
 | 
			
		||||
                bool nominal_length{ false };
 | 
			
		||||
                bool prepare_stage{ false };
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            EMoveType move_type{ EMoveType::Noop };
 | 
			
		||||
| 
						 | 
				
			
			@ -384,6 +390,8 @@ namespace Slic3r {
 | 
			
		|||
            std::array<float, static_cast<size_t>(EMoveType::Count)> moves_time;
 | 
			
		||||
            std::array<float, static_cast<size_t>(ExtrusionRole::erCount)> roles_time;
 | 
			
		||||
            std::vector<float> layers_time;
 | 
			
		||||
            //BBS: prepare stage time before print model, including start gcode time and mostly same with start gcode time
 | 
			
		||||
            float prepare_time;
 | 
			
		||||
 | 
			
		||||
            void reset();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -420,7 +428,7 @@ namespace Slic3r {
 | 
			
		|||
 | 
			
		||||
            // post process the file with the given filename to add remaining time lines M73
 | 
			
		||||
            // and updates moves' gcode ids accordingly
 | 
			
		||||
            void post_process(const std::string& filename, std::vector<GCodeProcessorResult::MoveVertex>& moves, std::vector<size_t>& lines_ends);
 | 
			
		||||
            void post_process(const std::string& filename, std::vector<GCodeProcessorResult::MoveVertex>& moves, std::vector<size_t>& lines_ends, size_t total_layer_num);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        struct UsedFilaments  // filaments per ColorChange
 | 
			
		||||
| 
						 | 
				
			
			@ -627,6 +635,7 @@ namespace Slic3r {
 | 
			
		|||
        SeamsDetector m_seams_detector;
 | 
			
		||||
        OptionsZCorrector m_options_z_corrector;
 | 
			
		||||
        size_t m_last_default_color_id;
 | 
			
		||||
        bool m_spiral_vase_active;
 | 
			
		||||
#if ENABLE_GCODE_VIEWER_STATISTICS
 | 
			
		||||
        std::chrono::time_point<std::chrono::high_resolution_clock> m_start_time;
 | 
			
		||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
 | 
			
		||||
| 
						 | 
				
			
			@ -684,6 +693,7 @@ namespace Slic3r {
 | 
			
		|||
        void finalize(bool post_process);
 | 
			
		||||
 | 
			
		||||
        float get_time(PrintEstimatedStatistics::ETimeMode mode) const;
 | 
			
		||||
        float get_prepare_time(PrintEstimatedStatistics::ETimeMode mode) const;
 | 
			
		||||
        std::string get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const;
 | 
			
		||||
        std::vector<std::pair<CustomGCode::Type, std::pair<float, float>>> get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -467,6 +467,8 @@ void process_perimeter_polygon(
 | 
			
		|||
        if (orig_point) {
 | 
			
		||||
            Vec3f pos_of_next      = orig_polygon_points.empty() ? first : orig_polygon_points.front();
 | 
			
		||||
            float distance_to_next = (position - pos_of_next).norm();
 | 
			
		||||
            if (distance_to_next > perimeter.flow_width * perimeter.flow_width * 4)
 | 
			
		||||
                oversampled_points.push((position + pos_of_next) / 2);
 | 
			
		||||
            if (global_model_info.is_enforced(position, distance_to_next)) {
 | 
			
		||||
                Vec3f vec_to_next = (pos_of_next - position).normalized();
 | 
			
		||||
                float step_size   = SeamPlacer::enforcer_oversampling_distance;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -175,10 +175,6 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
 | 
			
		|||
            for (auto layer : object->support_layers())
 | 
			
		||||
                zs.emplace_back(layer->print_z);
 | 
			
		||||
 | 
			
		||||
            // BBS
 | 
			
		||||
            for (auto layer : object->tree_support_layers())
 | 
			
		||||
                zs.emplace_back(layer->print_z);
 | 
			
		||||
 | 
			
		||||
            // Find first object layer that is not empty and save its print_z
 | 
			
		||||
            for (const Layer* layer : object->layers())
 | 
			
		||||
                if (layer->has_extrusions()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -199,10 +195,11 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
 | 
			
		|||
    // BBS
 | 
			
		||||
	if (auto num_filaments = unsigned(print.config().filament_diameter.size());
 | 
			
		||||
		num_filaments > 1 && print.object_extruders().size() == 1 && // the current Print's configuration is CustomGCode::MultiAsSingle
 | 
			
		||||
		print.model().custom_gcode_per_print_z.mode == CustomGCode::MultiAsSingle) {
 | 
			
		||||
        //BBS: replace model custom gcode with current plate custom gcode
 | 
			
		||||
        print.model().get_curr_plate_custom_gcodes().mode == CustomGCode::MultiAsSingle) {
 | 
			
		||||
		// Printing a single extruder platter on a printer with more than 1 extruder (or single-extruder multi-material).
 | 
			
		||||
		// There may be custom per-layer tool changes available at the model.
 | 
			
		||||
		per_layer_extruder_switches = custom_tool_changes(print.model().custom_gcode_per_print_z, num_filaments);
 | 
			
		||||
        per_layer_extruder_switches = custom_tool_changes(print.model().get_curr_plate_custom_gcodes(), num_filaments);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    // Collect extruders reuqired to print the layers.
 | 
			
		||||
| 
						 | 
				
			
			@ -348,24 +345,6 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // BBS
 | 
			
		||||
    for (auto tree_support_layer : object.tree_support_layers()) {
 | 
			
		||||
        LayerTools   &layer_tools = this->tools_for_layer(tree_support_layer->print_z);
 | 
			
		||||
        ExtrusionRole role = tree_support_layer->support_fills.role();
 | 
			
		||||
        bool         has_support        = role == erMixed || role == erSupportMaterial || role == erSupportTransition;;
 | 
			
		||||
        bool         has_interface      = role == erMixed || role == erSupportMaterialInterface;
 | 
			
		||||
        unsigned int extruder_support   = object.config().support_filament.value;
 | 
			
		||||
        unsigned int extruder_interface = object.config().support_interface_filament.value;
 | 
			
		||||
        if (has_support)
 | 
			
		||||
            layer_tools.extruders.push_back(extruder_support);
 | 
			
		||||
        if (has_interface)
 | 
			
		||||
            layer_tools.extruders.push_back(extruder_interface);
 | 
			
		||||
        if (has_support || has_interface) {
 | 
			
		||||
            layer_tools.has_support = true;
 | 
			
		||||
            layer_tools.wiping_extrusions().is_support_overriddable_and_mark(role, object);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Extruder overrides are ordered by print_z.
 | 
			
		||||
    std::vector<std::pair<double, unsigned int>>::const_iterator it_per_layer_extruder_override;
 | 
			
		||||
	it_per_layer_extruder_override = per_layer_extruder_switches.begin();
 | 
			
		||||
| 
						 | 
				
			
			@ -781,12 +760,15 @@ void ToolOrdering::mark_skirt_layers(const PrintConfig &config, coordf_t max_lay
 | 
			
		|||
// Assign a pointer to a custom G-code to the respective ToolOrdering::LayerTools.
 | 
			
		||||
// Ignore color changes, which are performed on a layer and for such an extruder, that the extruder will not be printing above that layer.
 | 
			
		||||
// If multiple events are planned over a span of a single layer, use the last one.
 | 
			
		||||
 | 
			
		||||
// BBS: replace model custom gcode with current plate custom gcode
 | 
			
		||||
static CustomGCode::Info custom_gcode_per_print_z;
 | 
			
		||||
void ToolOrdering::assign_custom_gcodes(const Print &print)
 | 
			
		||||
{
 | 
			
		||||
	// Only valid for non-sequential print.
 | 
			
		||||
	assert(print.config().print_sequence == PrintSequence::ByLayer);
 | 
			
		||||
 | 
			
		||||
	const CustomGCode::Info	&custom_gcode_per_print_z = print.model().custom_gcode_per_print_z;
 | 
			
		||||
    custom_gcode_per_print_z = print.model().get_curr_plate_custom_gcodes();
 | 
			
		||||
	if (custom_gcode_per_print_z.gcodes.empty())
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -795,7 +777,7 @@ void ToolOrdering::assign_custom_gcodes(const Print &print)
 | 
			
		|||
	CustomGCode::Mode 			mode          =
 | 
			
		||||
		(num_filaments == 1) ? CustomGCode::SingleExtruder :
 | 
			
		||||
		print.object_extruders().size() == 1 ? CustomGCode::MultiAsSingle : CustomGCode::MultiExtruder;
 | 
			
		||||
	CustomGCode::Mode           model_mode    = print.model().custom_gcode_per_print_z.mode;
 | 
			
		||||
    CustomGCode::Mode           model_mode    = print.model().get_curr_plate_custom_gcodes().mode;
 | 
			
		||||
	std::vector<unsigned char> 	extruder_printing_above(num_filaments, false);
 | 
			
		||||
	auto 						custom_gcode_it = custom_gcode_per_print_z.gcodes.rbegin();
 | 
			
		||||
	// Tool changes and color changes will be ignored, if the model's tool/color changes were entered in mm mode and the print is in non mm mode
 | 
			
		||||
| 
						 | 
				
			
			@ -890,7 +872,7 @@ int WipingExtrusions::first_nonsoluble_extruder_on_layer(const PrintConfig& prin
 | 
			
		|||
{
 | 
			
		||||
    const LayerTools& lt = *m_layer_tools;
 | 
			
		||||
    for (auto extruders_it = lt.extruders.begin(); extruders_it != lt.extruders.end(); ++extruders_it)
 | 
			
		||||
        if (!print_config.filament_soluble.get_at(*extruders_it))
 | 
			
		||||
        if (!print_config.filament_soluble.get_at(*extruders_it) && !print_config.filament_is_support.get_at(*extruders_it))
 | 
			
		||||
            return (*extruders_it);
 | 
			
		||||
 | 
			
		||||
    return (-1);
 | 
			
		||||
| 
						 | 
				
			
			@ -901,7 +883,7 @@ int WipingExtrusions::last_nonsoluble_extruder_on_layer(const PrintConfig& print
 | 
			
		|||
{
 | 
			
		||||
    const LayerTools& lt = *m_layer_tools;
 | 
			
		||||
    for (auto extruders_it = lt.extruders.rbegin(); extruders_it != lt.extruders.rend(); ++extruders_it)
 | 
			
		||||
        if (!print_config.filament_soluble.get_at(*extruders_it))
 | 
			
		||||
        if (!print_config.filament_soluble.get_at(*extruders_it) && !print_config.filament_is_support.get_at(*extruders_it))
 | 
			
		||||
            return (*extruders_it);
 | 
			
		||||
 | 
			
		||||
    return (-1);
 | 
			
		||||
| 
						 | 
				
			
			@ -1042,10 +1024,9 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
 | 
			
		|||
            if (object->config().flush_into_support) {
 | 
			
		||||
                auto& object_config = object->config();
 | 
			
		||||
                const SupportLayer* this_support_layer = object->get_support_layer_at_printz(lt.print_z, EPSILON);
 | 
			
		||||
                const TreeSupportLayer* this_tree_support_layer = object->get_tree_support_layer_at_printz(lt.print_z, EPSILON);
 | 
			
		||||
 | 
			
		||||
                do {
 | 
			
		||||
                    if (this_support_layer == nullptr && this_tree_support_layer == nullptr)
 | 
			
		||||
                    if (this_support_layer == nullptr)
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    bool support_overriddable = object_config.support_filament == 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -1053,7 +1034,7 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
 | 
			
		|||
                    if (!support_overriddable && !support_intf_overriddable)
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    auto& entities = this_support_layer != nullptr ? this_support_layer->support_fills.entities : this_tree_support_layer->support_fills.entities;
 | 
			
		||||
                    auto &entities = this_support_layer->support_fills.entities;
 | 
			
		||||
                    if (support_overriddable && !is_support_overridden(object)) {
 | 
			
		||||
                        set_support_extruder_override(object, copy, new_extruder, num_of_copies);
 | 
			
		||||
                        for (const ExtrusionEntity* ee : entities) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -398,14 +398,26 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
 | 
			
		|||
                slop_move = w0.string();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::string xy_z_move;
 | 
			
		||||
        {
 | 
			
		||||
            GCodeG1Formatter w0;
 | 
			
		||||
            if (this->is_current_position_clear()) {
 | 
			
		||||
                w0.emit_xyz(target);
 | 
			
		||||
                w0.emit_f(this->config.travel_speed.value * 60.0);
 | 
			
		||||
                w0.emit_comment(GCodeWriter::full_gcode_comment, comment);
 | 
			
		||||
                xy_z_move = w0.string();
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                w0.emit_xy(Vec2d(target.x(), target.y()));
 | 
			
		||||
                w0.emit_f(this->config.travel_speed.value * 60.0);
 | 
			
		||||
                w0.emit_comment(GCodeWriter::full_gcode_comment, comment);
 | 
			
		||||
                xy_z_move = w0.string() + _travel_to_z(target.z(), comment);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        m_pos = dest_point;
 | 
			
		||||
        this->set_current_position_clear(true);
 | 
			
		||||
        GCodeG1Formatter w1;
 | 
			
		||||
        w1.emit_xyz(target);
 | 
			
		||||
        w1.emit_f(this->config.travel_speed.value * 60.0);
 | 
			
		||||
        //BBS
 | 
			
		||||
        w1.emit_comment(GCodeWriter::full_gcode_comment, comment);
 | 
			
		||||
        return slop_move + w1.string();
 | 
			
		||||
        return slop_move + xy_z_move;
 | 
			
		||||
    }
 | 
			
		||||
    else if (!this->will_move_z(point(2))) {
 | 
			
		||||
        double nominal_z = m_pos(2) - m_lifted;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -79,7 +79,7 @@ public:
 | 
			
		|||
 | 
			
		||||
    //BBS: set offset for gcode writer
 | 
			
		||||
    void set_xy_offset(double x, double y) { m_x_offset = x; m_y_offset = y; }
 | 
			
		||||
 | 
			
		||||
    Vec2f get_xy_offset() { return Vec2f{m_x_offset, m_y_offset}; };
 | 
			
		||||
    // To be called by the CoolingBuffer from another thread.
 | 
			
		||||
    static std::string set_fan(const GCodeFlavor gcode_flavor, unsigned int speed);
 | 
			
		||||
    // To be called by the main thread. It always emits the G-code, it does not remember the previous state.
 | 
			
		||||
| 
						 | 
				
			
			@ -93,7 +93,8 @@ public:
 | 
			
		|||
    bool is_current_position_clear() const { return m_is_current_pos_clear; };
 | 
			
		||||
    //BBS:
 | 
			
		||||
    static const bool full_gcode_comment;
 | 
			
		||||
    
 | 
			
		||||
    //Radian threshold of slope for lazy lift and spiral lift;
 | 
			
		||||
    static const double slope_threshold;
 | 
			
		||||
    //SoftFever
 | 
			
		||||
    void set_is_bbl_machine(bool bval) {m_is_bbl_printers = bval;}
 | 
			
		||||
    const bool is_bbl_printers() const {return m_is_bbl_printers;}
 | 
			
		||||
| 
						 | 
				
			
			@ -134,9 +135,6 @@ private:
 | 
			
		|||
    double          m_x_offset{ 0 };
 | 
			
		||||
    double          m_y_offset{ 0 };
 | 
			
		||||
 | 
			
		||||
    //Radian threshold of slope for lazy lift and spiral lift;
 | 
			
		||||
    static const double slope_threshold;
 | 
			
		||||
    
 | 
			
		||||
    //SoftFever
 | 
			
		||||
    bool            m_is_bbl_printers = false;
 | 
			
		||||
    double          m_current_speed;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -352,6 +352,50 @@ void Layer::export_region_fill_surfaces_to_svg_debug(const char *name) const
 | 
			
		|||
    this->export_region_fill_surfaces_to_svg(debug_out_path("Layer-fill_surfaces-%s-%d.svg", name, idx ++).c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
coordf_t Layer::get_sparse_infill_max_void_area()
 | 
			
		||||
{
 | 
			
		||||
    double max_void_area = 0.;
 | 
			
		||||
    for (auto layerm : m_regions) {
 | 
			
		||||
        Flow flow = layerm->flow(frInfill);
 | 
			
		||||
        float density = layerm->region().config().sparse_infill_density;
 | 
			
		||||
        InfillPattern pattern = layerm->region().config().sparse_infill_pattern;
 | 
			
		||||
        if (density == 0.)
 | 
			
		||||
            return -1;
 | 
			
		||||
 | 
			
		||||
        //BBS: rough estimation and need to be optimized
 | 
			
		||||
        double spacing = flow.scaled_spacing() * (100 - density) / density;
 | 
			
		||||
        switch (pattern) {
 | 
			
		||||
            case ipConcentric:
 | 
			
		||||
            case ipRectilinear:
 | 
			
		||||
            case ipLine:
 | 
			
		||||
            case ipGyroid:
 | 
			
		||||
            case ipAlignedRectilinear:
 | 
			
		||||
            case ipOctagramSpiral:
 | 
			
		||||
            case ipHilbertCurve:
 | 
			
		||||
            case ip3DHoneycomb:
 | 
			
		||||
            case ipArchimedeanChords:
 | 
			
		||||
                max_void_area = std::max(max_void_area, spacing * spacing);
 | 
			
		||||
                break;
 | 
			
		||||
            case ipGrid:
 | 
			
		||||
            case ipHoneycomb:
 | 
			
		||||
            case ipLightning:
 | 
			
		||||
                max_void_area = std::max(max_void_area, 4.0 * spacing * spacing);
 | 
			
		||||
                break;
 | 
			
		||||
            case ipCubic:
 | 
			
		||||
            case ipAdaptiveCubic:
 | 
			
		||||
            case ipTriangles:
 | 
			
		||||
            case ipStars:
 | 
			
		||||
            case ipSupportCubic:
 | 
			
		||||
                max_void_area = std::max(max_void_area, 4.5 * spacing * spacing);
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                max_void_area = std::max(max_void_area, spacing * spacing);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    return max_void_area;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BoundingBox get_extents(const LayerRegion &layer_region)
 | 
			
		||||
{
 | 
			
		||||
    BoundingBox bbox;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -143,6 +143,9 @@ public:
 | 
			
		|||
    ExPolygons 				 lslices;
 | 
			
		||||
    std::vector<BoundingBox> lslices_bboxes;
 | 
			
		||||
 | 
			
		||||
    // BBS
 | 
			
		||||
    ExPolygons              loverhangs;
 | 
			
		||||
 | 
			
		||||
    size_t                  region_count() const { return m_regions.size(); }
 | 
			
		||||
    const LayerRegion*      get_region(int idx) const { return m_regions[idx]; }
 | 
			
		||||
    LayerRegion*            get_region(int idx) { return m_regions[idx]; }
 | 
			
		||||
| 
						 | 
				
			
			@ -184,6 +187,8 @@ public:
 | 
			
		|||
 | 
			
		||||
    //BBS
 | 
			
		||||
    void simplify_extrusion_path() { for (auto layerm : m_regions) layerm->simplify_extrusion_entity();}
 | 
			
		||||
    //BBS: this function calculate the maximum void grid area of sparse infill of this layer. Just estimated value
 | 
			
		||||
    coordf_t get_sparse_infill_max_void_area();
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    friend class PrintObject;
 | 
			
		||||
| 
						 | 
				
			
			@ -209,6 +214,11 @@ private:
 | 
			
		|||
    LayerRegionPtrs     m_regions;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum SupportInnerType {
 | 
			
		||||
    stInnerNormal,
 | 
			
		||||
    stInnerTree
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class SupportLayer : public Layer
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
| 
						 | 
				
			
			@ -217,6 +227,10 @@ public:
 | 
			
		|||
    ExPolygonCollection         support_islands;
 | 
			
		||||
    // Extrusion paths for the support base and for the support interface and contacts.
 | 
			
		||||
    ExtrusionEntityCollection   support_fills;
 | 
			
		||||
    SupportInnerType          support_type = stInnerNormal;
 | 
			
		||||
 | 
			
		||||
    // for tree supports
 | 
			
		||||
    ExPolygons base_areas;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // Is there any valid extrusion assigned to this LayerRegion?
 | 
			
		||||
| 
						 | 
				
			
			@ -229,33 +243,23 @@ public:
 | 
			
		|||
 | 
			
		||||
protected:
 | 
			
		||||
    friend class PrintObject;
 | 
			
		||||
    friend class TreeSupport;
 | 
			
		||||
 | 
			
		||||
    // The constructor has been made public to be able to insert additional support layers for the skirt or a wipe tower
 | 
			
		||||
    // between the raft and the object first layer.
 | 
			
		||||
    SupportLayer(size_t id, size_t interface_id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) :
 | 
			
		||||
        Layer(id, object, height, print_z, slice_z), m_interface_id(interface_id) {}
 | 
			
		||||
        Layer(id, object, height, print_z, slice_z), m_interface_id(interface_id), support_type(stInnerNormal) {}
 | 
			
		||||
    virtual ~SupportLayer() = default;
 | 
			
		||||
 | 
			
		||||
    size_t m_interface_id;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class TreeSupportLayer : public Layer
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    ExtrusionEntityCollection support_fills;
 | 
			
		||||
    ExPolygons overhang_areas;
 | 
			
		||||
    ExPolygons roof_areas;
 | 
			
		||||
    ExPolygons roof_1st_layer;  // the layer just below roof. When working with PolySupport, this layer should be printed with regular material
 | 
			
		||||
    ExPolygons floor_areas;
 | 
			
		||||
    ExPolygons base_areas;
 | 
			
		||||
    ExPolygons roof_gap_areas; // the areas in the gap between support roof and overhang
 | 
			
		||||
 | 
			
		||||
    enum AreaType {
 | 
			
		||||
        BaseType=0,
 | 
			
		||||
        RoofType=1,
 | 
			
		||||
        FloorType=2,
 | 
			
		||||
        Roof1stLayer=3
 | 
			
		||||
    };
 | 
			
		||||
    // for tree support
 | 
			
		||||
    ExPolygons                                overhang_areas;
 | 
			
		||||
    ExPolygons                                roof_areas;
 | 
			
		||||
    ExPolygons                                roof_1st_layer; // the layer just below roof. When working with PolySupport, this layer should be printed with regular material
 | 
			
		||||
    ExPolygons                                floor_areas;
 | 
			
		||||
    ExPolygons                                roof_gap_areas; // the areas in the gap between support roof and overhang
 | 
			
		||||
    enum AreaType { BaseType = 0, RoofType = 1, FloorType = 2, Roof1stLayer = 3 };
 | 
			
		||||
    struct AreaGroup
 | 
			
		||||
    {
 | 
			
		||||
        ExPolygon *area;
 | 
			
		||||
| 
						 | 
				
			
			@ -263,23 +267,9 @@ public:
 | 
			
		|||
        coordf_t   dist_to_top; // mm dist to top
 | 
			
		||||
        AreaGroup(ExPolygon *a, int t, coordf_t d) : area(a), type(t), dist_to_top(d) {}
 | 
			
		||||
    };
 | 
			
		||||
    std::vector<AreaGroup> area_groups;
 | 
			
		||||
 | 
			
		||||
    enum OverhangType {
 | 
			
		||||
        Detected=0,
 | 
			
		||||
        Enforced
 | 
			
		||||
    };
 | 
			
		||||
    enum OverhangType { Detected = 0, Enforced };
 | 
			
		||||
    std::vector<AreaGroup>                    area_groups;
 | 
			
		||||
    std::map<const ExPolygon *, OverhangType> overhang_types;
 | 
			
		||||
 | 
			
		||||
    virtual bool has_extrusions() const { return !support_fills.empty(); }
 | 
			
		||||
 | 
			
		||||
    void simplify_support_extrusion_path() { this->simplify_support_entity_collection(&support_fills);}
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    friend class PrintObject;
 | 
			
		||||
    TreeSupportLayer(size_t id, PrintObject* object, coordf_t height, coordf_t print_z, coordf_t slice_z) :
 | 
			
		||||
        Layer(id, object, height, print_z, slice_z) {}
 | 
			
		||||
    virtual ~TreeSupportLayer() = default;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename LayerContainer>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -158,11 +158,20 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
 | 
			
		|||
    {
 | 
			
		||||
        // Voids are sparse infills if infill rate is zero.
 | 
			
		||||
        Polygons voids;
 | 
			
		||||
 | 
			
		||||
        double max_grid_area = -1;
 | 
			
		||||
        if (this->layer()->lower_layer != nullptr)
 | 
			
		||||
            max_grid_area = this->layer()->lower_layer->get_sparse_infill_max_void_area();
 | 
			
		||||
        for (const Surface &surface : this->fill_surfaces.surfaces) {
 | 
			
		||||
            if (surface.is_top()) {
 | 
			
		||||
                // Collect the top surfaces, inflate them and trim them by the bottom surfaces.
 | 
			
		||||
                // This gives the priority to bottom surfaces.
 | 
			
		||||
                surfaces_append(top, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
 | 
			
		||||
                if (max_grid_area < 0 || surface.expolygon.area() < max_grid_area)
 | 
			
		||||
                    surfaces_append(top, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
 | 
			
		||||
                else
 | 
			
		||||
                    //BBS: Don't need to expand too much in this situation. Expand 3mm to eliminate hole and 1mm for contour
 | 
			
		||||
                    surfaces_append(top, intersection_ex(offset(surface.expolygon.contour, margin / 3.0, EXTERNAL_SURFACES_OFFSET_PARAMETERS),
 | 
			
		||||
                                                         offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS)), surface);
 | 
			
		||||
            } else if (surface.surface_type == stBottom || (surface.surface_type == stBottomBridge && lower_layer == nullptr)) {
 | 
			
		||||
                // Grown by 3mm.
 | 
			
		||||
                surfaces_append(bottom, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -63,7 +63,9 @@ Model& Model::assign_copy(const Model &rhs)
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    // copy custom code per height
 | 
			
		||||
    this->custom_gcode_per_print_z = rhs.custom_gcode_per_print_z;
 | 
			
		||||
    // BBS
 | 
			
		||||
    this->plates_custom_gcodes = rhs.plates_custom_gcodes;
 | 
			
		||||
    this->curr_plate_index = rhs.curr_plate_index;
 | 
			
		||||
 | 
			
		||||
    // BBS: for design info
 | 
			
		||||
    this->design_info = rhs.design_info;
 | 
			
		||||
| 
						 | 
				
			
			@ -89,7 +91,9 @@ Model& Model::assign_copy(Model &&rhs)
 | 
			
		|||
    rhs.objects.clear();
 | 
			
		||||
 | 
			
		||||
    // copy custom code per height
 | 
			
		||||
    this->custom_gcode_per_print_z = std::move(rhs.custom_gcode_per_print_z);
 | 
			
		||||
    // BBS
 | 
			
		||||
    this->plates_custom_gcodes = std::move(rhs.plates_custom_gcodes);
 | 
			
		||||
    this->curr_plate_index = rhs.curr_plate_index;
 | 
			
		||||
 | 
			
		||||
    //BBS: add auxiliary path logic
 | 
			
		||||
    // BBS: backup, all in one temp dir
 | 
			
		||||
| 
						 | 
				
			
			@ -161,10 +165,11 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
 | 
			
		|||
        file_version = &temp_version;
 | 
			
		||||
 | 
			
		||||
    bool result = false;
 | 
			
		||||
    bool is_cb_cancel = false;
 | 
			
		||||
    std::string message;
 | 
			
		||||
    if (boost::algorithm::iends_with(input_file, ".stp") ||
 | 
			
		||||
        boost::algorithm::iends_with(input_file, ".step"))
 | 
			
		||||
        result = load_step(input_file.c_str(), &model, stepFn, stepIsUtf8Fn);
 | 
			
		||||
        result = load_step(input_file.c_str(), &model, is_cb_cancel, stepFn, stepIsUtf8Fn);
 | 
			
		||||
    else if (boost::algorithm::iends_with(input_file, ".stl"))
 | 
			
		||||
        result = load_stl(input_file.c_str(), &model, nullptr, stlFn);
 | 
			
		||||
    else if (boost::algorithm::iends_with(input_file, ".obj"))
 | 
			
		||||
| 
						 | 
				
			
			@ -185,6 +190,11 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
 | 
			
		|||
    else
 | 
			
		||||
        throw Slic3r::RuntimeError("Unknown file format. Input file must have .stl, .obj, .amf(.xml) extension.");
 | 
			
		||||
 | 
			
		||||
    if (is_cb_cancel) {
 | 
			
		||||
        Model empty_model;
 | 
			
		||||
        return empty_model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!result) {
 | 
			
		||||
        if (message.empty())
 | 
			
		||||
            throw Slic3r::RuntimeError("Loading of a model file failed.");
 | 
			
		||||
| 
						 | 
				
			
			@ -203,7 +213,9 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
 | 
			
		|||
 | 
			
		||||
    //BBS
 | 
			
		||||
    //CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config);
 | 
			
		||||
    CustomGCode::check_mode_for_custom_gcode_per_print_z(model.custom_gcode_per_print_z);
 | 
			
		||||
    //BBS
 | 
			
		||||
    for (auto& plate_gcodes : model.plates_custom_gcodes)
 | 
			
		||||
        CustomGCode::check_mode_for_custom_gcode_per_print_z(plate_gcodes.second);
 | 
			
		||||
 | 
			
		||||
    sort_remove_duplicates(config_substitutions->substitutions);
 | 
			
		||||
    return model;
 | 
			
		||||
| 
						 | 
				
			
			@ -277,7 +289,9 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
 | 
			
		|||
            throw Slic3r::RuntimeError("Canceled");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    CustomGCode::check_mode_for_custom_gcode_per_print_z(model.custom_gcode_per_print_z);
 | 
			
		||||
    //BBS
 | 
			
		||||
    for (auto& plate_gcodes : model.plates_custom_gcodes)
 | 
			
		||||
        CustomGCode::check_mode_for_custom_gcode_per_print_z(plate_gcodes.second);
 | 
			
		||||
 | 
			
		||||
    BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("import 3mf IMPORT_STAGE_CHECK_MODE_GCODE\n");
 | 
			
		||||
    if (proFn) {
 | 
			
		||||
| 
						 | 
				
			
			@ -2352,6 +2366,12 @@ bool ModelVolume::is_splittable() const
 | 
			
		|||
// BBS
 | 
			
		||||
std::vector<int> ModelVolume::get_extruders() const
 | 
			
		||||
{
 | 
			
		||||
    if (m_type == ModelVolumeType::INVALID
 | 
			
		||||
        || m_type == ModelVolumeType::NEGATIVE_VOLUME
 | 
			
		||||
        || m_type == ModelVolumeType::SUPPORT_BLOCKER
 | 
			
		||||
        || m_type == ModelVolumeType::SUPPORT_ENFORCER)
 | 
			
		||||
        return std::vector<int>();
 | 
			
		||||
 | 
			
		||||
    if (mmu_segmentation_facets.timestamp() != mmuseg_ts) {
 | 
			
		||||
        std::vector<indexed_triangle_set> its_per_type;
 | 
			
		||||
        mmuseg_extruders.clear();
 | 
			
		||||
| 
						 | 
				
			
			@ -2564,6 +2584,7 @@ size_t ModelVolume::split(unsigned int max_extruders)
 | 
			
		|||
        if (idx == 0) {
 | 
			
		||||
            this->set_mesh(std::move(mesh));
 | 
			
		||||
            this->calculate_convex_hull();
 | 
			
		||||
            this->invalidate_convex_hull_2d();
 | 
			
		||||
            // Assign a new unique ID, so that a new GLVolume will be generated.
 | 
			
		||||
            this->set_new_unique_id();
 | 
			
		||||
            // reset the source to disable reload from disk
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -650,6 +650,38 @@ private:
 | 
			
		|||
    friend class ModelVolume;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct RaycastResult
 | 
			
		||||
{
 | 
			
		||||
    Vec2d mouse_position = Vec2d::Zero();
 | 
			
		||||
    int   mesh_id        = -1;
 | 
			
		||||
    Vec3f hit            = Vec3f::Zero();
 | 
			
		||||
    Vec3f normal         = Vec3f::Zero();
 | 
			
		||||
 | 
			
		||||
    template<typename Archive> void serialize(Archive &ar) { ar(mouse_position, mesh_id, hit, normal); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct TextInfo
 | 
			
		||||
{
 | 
			
		||||
    std::string m_font_name;
 | 
			
		||||
    float       m_font_size     = 16.f;
 | 
			
		||||
    int         m_curr_font_idx = 0;
 | 
			
		||||
    bool        m_bold          = true;
 | 
			
		||||
    bool        m_italic        = false;
 | 
			
		||||
    float       m_thickness     = 2.f;
 | 
			
		||||
    float       m_embeded_depth = 0.f;
 | 
			
		||||
    float       m_rotate_angle    = 0;
 | 
			
		||||
    float       m_text_gap        = 0.f;
 | 
			
		||||
    bool        m_is_surface_text = false;
 | 
			
		||||
    bool        m_keep_horizontal = false;
 | 
			
		||||
    std::string m_text;
 | 
			
		||||
 | 
			
		||||
    RaycastResult m_rr;
 | 
			
		||||
 | 
			
		||||
    template<typename Archive> void serialize(Archive &ar) {
 | 
			
		||||
        ar(m_font_name, m_font_size, m_curr_font_idx, m_bold, m_italic, m_thickness, m_embeded_depth, m_rotate_angle, m_text_gap, m_is_surface_text, m_keep_horizontal, m_text, m_rr);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// An object STL, or a modifier volume, over which a different set of parameters shall be applied.
 | 
			
		||||
// ModelVolume instances are owned by a ModelObject.
 | 
			
		||||
class ModelVolume final : public ObjectBase
 | 
			
		||||
| 
						 | 
				
			
			@ -756,6 +788,10 @@ public:
 | 
			
		|||
    const std::shared_ptr<const TriangleMesh>& get_convex_hull_shared_ptr() const { return m_convex_hull; }
 | 
			
		||||
    //BBS: add convex_hell_2d related logic
 | 
			
		||||
    const Polygon& get_convex_hull_2d(const Transform3d &trafo_instance) const;
 | 
			
		||||
    void invalidate_convex_hull_2d()
 | 
			
		||||
    {
 | 
			
		||||
        m_convex_hull_2d.clear();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Get count of errors in the mesh
 | 
			
		||||
    int                 get_repaired_errors_count() const;
 | 
			
		||||
| 
						 | 
				
			
			@ -795,6 +831,9 @@ public:
 | 
			
		|||
    void convert_from_imperial_units();
 | 
			
		||||
    void convert_from_meters();
 | 
			
		||||
 | 
			
		||||
    void set_text_info(const TextInfo& text_info) { m_text_info = text_info; }
 | 
			
		||||
    const TextInfo& get_text_info() const { return m_text_info; }
 | 
			
		||||
 | 
			
		||||
    const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); }
 | 
			
		||||
 | 
			
		||||
	void set_new_unique_id() {
 | 
			
		||||
| 
						 | 
				
			
			@ -839,6 +878,8 @@ private:
 | 
			
		|||
    mutable Polygon                     m_cached_2d_polygon;   //BBS, used for convex_hell_2d acceleration
 | 
			
		||||
    Geometry::Transformation        	m_transformation;
 | 
			
		||||
 | 
			
		||||
    TextInfo m_text_info;
 | 
			
		||||
 | 
			
		||||
    //BBS: add convex_hell_2d related logic
 | 
			
		||||
    void  calculate_convex_hull_2d(const Geometry::Transformation &transformation) const;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -892,7 +933,8 @@ private:
 | 
			
		|||
        ObjectBase(other),
 | 
			
		||||
        name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull),
 | 
			
		||||
        config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
 | 
			
		||||
        supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets)
 | 
			
		||||
        supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets),
 | 
			
		||||
        m_text_info(other.m_text_info)
 | 
			
		||||
    {
 | 
			
		||||
		assert(this->id().valid());
 | 
			
		||||
        assert(this->config.id().valid());
 | 
			
		||||
| 
						 | 
				
			
			@ -957,7 +999,7 @@ private:
 | 
			
		|||
        // BBS: add backup, check modify
 | 
			
		||||
        bool mesh_changed = false;
 | 
			
		||||
        auto tr = m_transformation;
 | 
			
		||||
        ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
 | 
			
		||||
        ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, m_text_info);
 | 
			
		||||
        mesh_changed |= !(tr == m_transformation);
 | 
			
		||||
        if (mesh_changed) m_transformation.get_matrix(true, true, true, true); // force dirty
 | 
			
		||||
        auto t = supported_facets.timestamp();
 | 
			
		||||
| 
						 | 
				
			
			@ -983,7 +1025,7 @@ private:
 | 
			
		|||
	}
 | 
			
		||||
	template<class Archive> void save(Archive &ar) const {
 | 
			
		||||
		bool has_convex_hull = m_convex_hull.get() != nullptr;
 | 
			
		||||
        ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
 | 
			
		||||
        ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, m_text_info);
 | 
			
		||||
        cereal::save_by_value(ar, supported_facets);
 | 
			
		||||
        cereal::save_by_value(ar, seam_facets);
 | 
			
		||||
        cereal::save_by_value(ar, mmu_segmentation_facets);
 | 
			
		||||
| 
						 | 
				
			
			@ -1280,7 +1322,17 @@ public:
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    // Extensions for color print
 | 
			
		||||
    CustomGCode::Info custom_gcode_per_print_z;
 | 
			
		||||
    // CustomGCode::Info custom_gcode_per_print_z;
 | 
			
		||||
    //BBS: replace model custom gcode with current plate custom gcode
 | 
			
		||||
    int curr_plate_index{ 0 };
 | 
			
		||||
    std::map<int, CustomGCode::Info> plates_custom_gcodes; //map<plate_index, CustomGCode::Info>
 | 
			
		||||
 | 
			
		||||
    const CustomGCode::Info get_curr_plate_custom_gcodes() const {
 | 
			
		||||
        if (plates_custom_gcodes.find(curr_plate_index) != plates_custom_gcodes.end()) {
 | 
			
		||||
            return plates_custom_gcodes.at(curr_plate_index);
 | 
			
		||||
        }
 | 
			
		||||
        return CustomGCode::Info();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Default constructor assigns a new ID to the model.
 | 
			
		||||
    Model() { assert(this->id().valid()); }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1136,21 +1136,8 @@ static std::vector<std::vector<const MMU_Graph::Arc *>> get_all_next_arcs(
 | 
			
		|||
            continue;
 | 
			
		||||
 | 
			
		||||
        Vec2d arc_line = graph.nodes[arc.to_idx].point - graph.nodes[arc.from_idx].point;
 | 
			
		||||
        if (arc_line.norm() < 5) { // two points whose distance is less than 5 are considered as one point
 | 
			
		||||
            Linef process_line_1(graph.nodes[arc.from_idx].point, graph.nodes[arc.to_idx].point);
 | 
			
		||||
            std::vector<std::vector<const MMU_Graph::Arc *>> next_arcs = get_all_next_arcs(graph, used_arcs, process_line_1, arc, color);
 | 
			
		||||
            if (next_arcs.empty())
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            for (std::vector<const MMU_Graph::Arc *> &next_arc : next_arcs) {
 | 
			
		||||
                next_continue_arc.emplace_back(&arc);
 | 
			
		||||
                next_continue_arc.insert(next_continue_arc.end(), next_arc.begin(), next_arc.end());
 | 
			
		||||
                all_next_arcs.emplace_back(next_continue_arc);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            next_continue_arc.emplace_back(&arc);
 | 
			
		||||
            all_next_arcs.emplace_back(next_continue_arc);
 | 
			
		||||
        }
 | 
			
		||||
        next_continue_arc.emplace_back(&arc);
 | 
			
		||||
        all_next_arcs.emplace_back(next_continue_arc);
 | 
			
		||||
    }
 | 
			
		||||
    return all_next_arcs;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -965,26 +965,19 @@ void PerimeterGenerator::process_classic()
 | 
			
		|||
            if (is_outer_wall_first ||
 | 
			
		||||
                //BBS: always print outer wall first when there indeed has brim.
 | 
			
		||||
                (this->layer_id == 0 &&
 | 
			
		||||
                    this->object_config->brim_type == BrimType::btOuterOnly &&
 | 
			
		||||
                    this->object_config->brim_width.value > 0))
 | 
			
		||||
            {
 | 
			
		||||
                if (this->config->wall_infill_order == WallInfillOrder::InnerOuterInnerInfill) {
 | 
			
		||||
                    if (entities.entities.size() > 1) {
 | 
			
		||||
                        std::vector<int> extPs;
 | 
			
		||||
                        for (int i = 0; i < entities.entities.size(); ++i) {
 | 
			
		||||
                            if (entities.entities[i]->role() == erExternalPerimeter)
 | 
			
		||||
                                extPs.push_back(i);
 | 
			
		||||
                 this->object_config->brim_type == BrimType::btOuterOnly &&
 | 
			
		||||
                 this->object_config->brim_width.value > 0))
 | 
			
		||||
                entities.reverse();
 | 
			
		||||
            else if (this->config->wall_infill_order == WallInfillOrder::InnerOuterInnerInfill)
 | 
			
		||||
                if (entities.entities.size() > 1){
 | 
			
		||||
                    int              last_outer=0;
 | 
			
		||||
                    int              outer = 0;
 | 
			
		||||
                    for (; outer < entities.entities.size(); ++outer)
 | 
			
		||||
                        if (entities.entities[outer]->role() == erExternalPerimeter && outer - last_outer > 1) {
 | 
			
		||||
                            std::swap(entities.entities[outer], entities.entities[outer - 1]);
 | 
			
		||||
                            last_outer = outer;
 | 
			
		||||
                        }
 | 
			
		||||
                        for (int i = 0; i < extPs.size(); ++i) {
 | 
			
		||||
                            if (extPs[i] == 0 || (i > 0 && extPs[i] - 1 == extPs[i - 1]))
 | 
			
		||||
                                continue;
 | 
			
		||||
                            std::swap(entities.entities[extPs[i]], entities.entities[extPs[i] - 1]);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                    entities.reverse();
 | 
			
		||||
            }
 | 
			
		||||
            // append perimeters for this slice as a collection
 | 
			
		||||
            if (! entities.empty())
 | 
			
		||||
                this->loops->append(entities);
 | 
			
		||||
| 
						 | 
				
			
			@ -1327,6 +1320,17 @@ void PerimeterGenerator::process_arachne()
 | 
			
		|||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // BBS. adjust wall generate seq
 | 
			
		||||
        if (this->config->wall_infill_order == WallInfillOrder::InnerOuterInnerInfill)
 | 
			
		||||
            if (ordered_extrusions.size() > 1) {
 | 
			
		||||
                int last_outer = 0;
 | 
			
		||||
                int outer      = 0;
 | 
			
		||||
                for (; outer < ordered_extrusions.size(); ++outer)
 | 
			
		||||
                    if (ordered_extrusions[outer].extrusion->inset_idx == 0 && outer - last_outer > 1) {
 | 
			
		||||
                        std::swap(ordered_extrusions[outer], ordered_extrusions[outer - 1]);
 | 
			
		||||
                        last_outer = outer;
 | 
			
		||||
                    }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        if (this->config->wall_infill_order == WallInfillOrder::InnerOuterInnerInfill) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -161,6 +161,21 @@ public:
 | 
			
		|||
    Point& operator-=(const Point& rhs) { this->x() -= rhs.x(); this->y() -= rhs.y(); return *this; }
 | 
			
		||||
	Point& operator*=(const double &rhs) { this->x() = coord_t(this->x() * rhs); this->y() = coord_t(this->y() * rhs); return *this; }
 | 
			
		||||
    Point operator*(const double &rhs) { return Point(this->x() * rhs, this->y() * rhs); }
 | 
			
		||||
    bool   both_comp(const Point &rhs, const std::string& op) { 
 | 
			
		||||
        if (op == ">")
 | 
			
		||||
            return this->x() > rhs.x() && this->y() > rhs.y();
 | 
			
		||||
        else if (op == "<")
 | 
			
		||||
            return this->x() < rhs.x() && this->y() < rhs.y();
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    bool any_comp(const Point &rhs, const std::string &op)
 | 
			
		||||
    {
 | 
			
		||||
        if (op == ">")
 | 
			
		||||
            return this->x() > rhs.x() || this->y() > rhs.y();
 | 
			
		||||
        else if (op == "<")
 | 
			
		||||
            return this->x() < rhs.x() || this->y() < rhs.y();
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void   rotate(double angle) { this->rotate(std::cos(angle), std::sin(angle)); }
 | 
			
		||||
    void   rotate(double cos_a, double sin_a) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -771,7 +771,7 @@ static std::vector<std::string> s_Preset_filament_options {
 | 
			
		|||
    "fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "overhang_fan_threshold", "close_fan_the_first_x_layers", "full_fan_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed",
 | 
			
		||||
    "filament_start_gcode", "filament_end_gcode",
 | 
			
		||||
    // Retract overrides
 | 
			
		||||
    "filament_retraction_length", "filament_z_hop", "filament_retraction_speed", "filament_deretraction_speed", "filament_retract_restart_extra", "filament_retraction_minimum_travel",
 | 
			
		||||
    "filament_retraction_length", "filament_z_hop", "filament_z_hop_types", "filament_retraction_speed", "filament_deretraction_speed", "filament_retract_restart_extra", "filament_retraction_minimum_travel",
 | 
			
		||||
    "filament_retract_when_changing_layer", "filament_wipe", "filament_retract_before_wipe",
 | 
			
		||||
    // Profile compatibility
 | 
			
		||||
    "filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits",
 | 
			
		||||
| 
						 | 
				
			
			@ -799,7 +799,7 @@ static std::vector<std::string> s_Preset_printer_options {
 | 
			
		|||
    "silent_mode",
 | 
			
		||||
    // BBS
 | 
			
		||||
    "scan_first_layer", "machine_load_filament_time", "machine_unload_filament_time", "machine_pause_gcode", "template_custom_gcode",
 | 
			
		||||
    "nozzle_type", "nozzle_hrc","auxiliary_fan", "nozzle_volume","upward_compatible_machine",
 | 
			
		||||
    "nozzle_type", "nozzle_hrc","auxiliary_fan", "nozzle_volume","upward_compatible_machine", "z_hop_types",
 | 
			
		||||
    //SoftFever
 | 
			
		||||
    "host_type", "print_host", "printhost_apikey", 
 | 
			
		||||
    "printhost_cafile","printhost_port","printhost_authorization_type",
 | 
			
		||||
| 
						 | 
				
			
			@ -1128,7 +1128,7 @@ void PresetCollection::load_presets(
 | 
			
		|||
    std::sort(m_presets.begin() + m_num_default_presets, m_presets.end());
 | 
			
		||||
    //BBS: add config related logs
 | 
			
		||||
    BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": loaded %1% presets from %2%, type %3%")%presets_loaded.size() %dir %Preset::get_type_string(m_type);
 | 
			
		||||
    this->select_preset(first_visible_idx());
 | 
			
		||||
    //this->select_preset(first_visible_idx());
 | 
			
		||||
    if (! errors_cummulative.empty())
 | 
			
		||||
        throw Slic3r::RuntimeError(errors_cummulative);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1555,6 +1555,10 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
 | 
			
		|||
            BOOST_LOG_TRIVIAL(error) << "Error in a preset file: The preset \"" <<
 | 
			
		||||
                name << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed";
 | 
			
		||||
        if (need_update) {
 | 
			
		||||
            if (iter->name == m_edited_preset.name && iter->is_dirty) {
 | 
			
		||||
                // Keep modifies when update from remote
 | 
			
		||||
                new_config.apply_only(m_edited_preset.config, m_edited_preset.config.diff(iter->config));
 | 
			
		||||
            }
 | 
			
		||||
            iter->config = new_config;
 | 
			
		||||
            iter->updated_time = cloud_update_time;
 | 
			
		||||
            iter->version = cloud_version.value();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -241,44 +241,17 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward
 | 
			
		|||
    //BBS: change system config to json
 | 
			
		||||
    std::tie(substitutions, errors_cummulative) = this->load_system_presets_from_json(substitution_rule);
 | 
			
		||||
 | 
			
		||||
    //BBS load preset from user's folder, load system default if
 | 
			
		||||
    //BBS: change directories by design
 | 
			
		||||
    std::string dir_user_presets;
 | 
			
		||||
    if (!config.get("preset_folder").empty()) {
 | 
			
		||||
        dir_user_presets = data_dir() + "/" + PRESET_USER_DIR + "/" + config.get("preset_folder");
 | 
			
		||||
    // Load default user presets always
 | 
			
		||||
    load_user_presets(DEFAULT_USER_FOLDER_NAME, substitution_rule);
 | 
			
		||||
    // BBS load preset from user's folder, load system default if
 | 
			
		||||
    // BBS: change directories by design
 | 
			
		||||
    std::string dir_user_presets = config.get("preset_folder");
 | 
			
		||||
    if (!dir_user_presets.empty()) {
 | 
			
		||||
        load_user_presets(dir_user_presets, substitution_rule);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        dir_user_presets = data_dir() + "/" + PRESET_USER_DIR + "/" + DEFAULT_USER_FOLDER_NAME;
 | 
			
		||||
    }
 | 
			
		||||
    fs::path user_folder(data_dir() + "/" + PRESET_USER_DIR);
 | 
			
		||||
    if (!fs::exists(user_folder))
 | 
			
		||||
        fs::create_directory(user_folder);
 | 
			
		||||
 | 
			
		||||
    fs::path folder(dir_user_presets);
 | 
			
		||||
    if (!fs::exists(folder))
 | 
			
		||||
        fs::create_directory(folder);
 | 
			
		||||
 | 
			
		||||
    // BBS do not load sla_print
 | 
			
		||||
    //BBS: change directoties by design
 | 
			
		||||
    try {
 | 
			
		||||
        this->prints.load_presets(dir_user_presets, PRESET_PRINT_NAME, substitutions, substitution_rule);
 | 
			
		||||
    } catch (const std::runtime_error &err) {
 | 
			
		||||
        errors_cummulative += err.what();
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
        this->filaments.load_presets(dir_user_presets, PRESET_FILAMENT_NAME, substitutions, substitution_rule);
 | 
			
		||||
    } catch (const std::runtime_error &err) {
 | 
			
		||||
        errors_cummulative += err.what();
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
        this->printers.load_presets(dir_user_presets, PRESET_PRINTER_NAME, substitutions, substitution_rule);
 | 
			
		||||
    } catch (const std::runtime_error &err) {
 | 
			
		||||
        errors_cummulative += err.what();
 | 
			
		||||
    }
 | 
			
		||||
    this->update_multi_material_filament_presets();
 | 
			
		||||
    this->update_compatible(PresetSelectCompatibleType::Never);
 | 
			
		||||
    if (! errors_cummulative.empty())
 | 
			
		||||
        throw Slic3r::RuntimeError(errors_cummulative);
 | 
			
		||||
 | 
			
		||||
    this->load_selections(config, preferred_selection);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -534,7 +507,44 @@ std::string PresetBundle::get_hotend_model_for_printer_model(std::string model_n
 | 
			
		|||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PresetsConfigSubstitutions PresetBundle::load_user_presets(AppConfig &config, std::map<std::string, std::map<std::string, std::string>>& my_presets, ForwardCompatibilitySubstitutionRule substitution_rule)
 | 
			
		||||
PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, ForwardCompatibilitySubstitutionRule substitution_rule)
 | 
			
		||||
{
 | 
			
		||||
    PresetsConfigSubstitutions substitutions;
 | 
			
		||||
    std::string errors_cummulative;
 | 
			
		||||
 | 
			
		||||
    fs::path user_folder(data_dir() + "/" + PRESET_USER_DIR);
 | 
			
		||||
    if (!fs::exists(user_folder)) fs::create_directory(user_folder);
 | 
			
		||||
 | 
			
		||||
    std::string dir_user_presets = data_dir() + "/" + PRESET_USER_DIR + "/" + user;
 | 
			
		||||
    fs::path    folder(user_folder / user);
 | 
			
		||||
    if (!fs::exists(folder)) fs::create_directory(folder);
 | 
			
		||||
 | 
			
		||||
    // BBS do not load sla_print
 | 
			
		||||
    // BBS: change directoties by design
 | 
			
		||||
    try {
 | 
			
		||||
        this->prints.load_presets(dir_user_presets, PRESET_PRINT_NAME, substitutions, substitution_rule);
 | 
			
		||||
    } catch (const std::runtime_error &err) {
 | 
			
		||||
        errors_cummulative += err.what();
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
        this->filaments.load_presets(dir_user_presets, PRESET_FILAMENT_NAME, substitutions, substitution_rule);
 | 
			
		||||
    } catch (const std::runtime_error &err) {
 | 
			
		||||
        errors_cummulative += err.what();
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
        this->printers.load_presets(dir_user_presets, PRESET_PRINTER_NAME, substitutions, substitution_rule);
 | 
			
		||||
    } catch (const std::runtime_error &err) {
 | 
			
		||||
        errors_cummulative += err.what();
 | 
			
		||||
    }
 | 
			
		||||
    if (!errors_cummulative.empty()) throw Slic3r::RuntimeError(errors_cummulative);
 | 
			
		||||
    this->update_multi_material_filament_presets();
 | 
			
		||||
    this->update_compatible(PresetSelectCompatibleType::Never);
 | 
			
		||||
    return PresetsConfigSubstitutions();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PresetsConfigSubstitutions PresetBundle::load_user_presets(AppConfig &                                                config,
 | 
			
		||||
                                                           std::map<std::string, std::map<std::string, std::string>> &my_presets,
 | 
			
		||||
                                                           ForwardCompatibilitySubstitutionRule                       substitution_rule)
 | 
			
		||||
{
 | 
			
		||||
    // First load the vendor specific system presets.
 | 
			
		||||
    PresetsConfigSubstitutions substitutions;
 | 
			
		||||
| 
						 | 
				
			
			@ -545,6 +555,10 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(AppConfig &config, st
 | 
			
		|||
    BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" print's selected_idx %1%, selected_name %2%") %prints.get_selected_idx() %prints.get_selected_preset_name();
 | 
			
		||||
    BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" filament's selected_idx %1%, selected_name %2%") %filaments.get_selected_idx() %filaments.get_selected_preset_name();
 | 
			
		||||
    BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" printers's selected_idx %1%, selected_name %2%") %printers.get_selected_idx() %printers.get_selected_preset_name();
 | 
			
		||||
 | 
			
		||||
    // Sync removing
 | 
			
		||||
    remove_users_preset(config, &my_presets);
 | 
			
		||||
 | 
			
		||||
    std::map<std::string, std::map<std::string, std::string>>::iterator it;
 | 
			
		||||
    for (it = my_presets.begin(); it != my_presets.end(); it++) {
 | 
			
		||||
        std::string name = it->first;
 | 
			
		||||
| 
						 | 
				
			
			@ -830,13 +844,26 @@ bool PresetBundle::validate_printers(const std::string &name, DynamicPrintConfig
 | 
			
		|||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PresetBundle::remove_users_preset(AppConfig& config)
 | 
			
		||||
void PresetBundle::remove_users_preset(AppConfig &config, std::map<std::string, std::map<std::string, std::string>> *my_presets)
 | 
			
		||||
{
 | 
			
		||||
    auto check_removed = [my_presets, this](Preset &preset) -> bool {
 | 
			
		||||
        if (my_presets == nullptr) return true;
 | 
			
		||||
        if (my_presets->find(preset.name) != my_presets->end()) return false;
 | 
			
		||||
        if (!preset.sync_info.empty()) return false; // syncing, not remove
 | 
			
		||||
        if (preset.setting_id.empty()) return false; // no id, not remove
 | 
			
		||||
        // Saved preset is removed by another session
 | 
			
		||||
        if (preset.is_dirty) {
 | 
			
		||||
            preset.setting_id.clear();
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        preset.remove_files();
 | 
			
		||||
        return true;
 | 
			
		||||
    };
 | 
			
		||||
    std::string preset_folder_user_id = config.get("preset_folder");
 | 
			
		||||
    std::string printer_selected_preset_name = printers.get_selected_preset().name;
 | 
			
		||||
    bool need_reset_printer_preset = false;
 | 
			
		||||
    for (auto it = printers.begin(); it != printers.end();) {
 | 
			
		||||
        if (it->is_user() && !it->user_id.empty() && it->user_id.compare(preset_folder_user_id) == 0) {
 | 
			
		||||
        if (it->is_user() && !it->user_id.empty() && it->user_id.compare(preset_folder_user_id) == 0 && check_removed(*it)) {
 | 
			
		||||
            BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":printers erase %1%, type %2%, user_id %3%") % it->name % Preset::get_type_string(it->type) % it->user_id;
 | 
			
		||||
            if (it->name == printer_selected_preset_name)
 | 
			
		||||
                need_reset_printer_preset = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -867,7 +894,7 @@ void PresetBundle::remove_users_preset(AppConfig& config)
 | 
			
		|||
    bool need_reset_print_preset = false;
 | 
			
		||||
    // remove preset if user_id is not current user
 | 
			
		||||
    for (auto it = prints.begin(); it != prints.end();) {
 | 
			
		||||
        if (it->is_user() && !it->user_id.empty() && it->user_id.compare(preset_folder_user_id) == 0) {
 | 
			
		||||
        if (it->is_user() && !it->user_id.empty() && it->user_id.compare(preset_folder_user_id) == 0 && check_removed(*it)) {
 | 
			
		||||
            BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":prints erase %1%, type %2%, user_id %3%")%it->name %Preset::get_type_string(it->type) %it->user_id;
 | 
			
		||||
            if (it->name == selected_print_name)
 | 
			
		||||
                need_reset_print_preset = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -887,7 +914,7 @@ void PresetBundle::remove_users_preset(AppConfig& config)
 | 
			
		|||
    std::string selected_filament_name = filaments.get_selected_preset().name;
 | 
			
		||||
    bool need_reset_filament_preset = false;
 | 
			
		||||
    for (auto it = filaments.begin(); it != filaments.end();) {
 | 
			
		||||
        if (it->is_user() && !it->user_id.empty() && it->user_id.compare(preset_folder_user_id) == 0) {
 | 
			
		||||
        if (it->is_user() && !it->user_id.empty() && it->user_id.compare(preset_folder_user_id) == 0 && check_removed(*it)) {
 | 
			
		||||
            BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":filaments erase %1%, type %2%, user_id %3%")%it->name %Preset::get_type_string(it->type) %it->user_id;
 | 
			
		||||
            if (it->name == selected_filament_name)
 | 
			
		||||
                need_reset_filament_preset = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -905,6 +932,8 @@ void PresetBundle::remove_users_preset(AppConfig& config)
 | 
			
		|||
        filaments.select_preset_by_name(selected_filament_name, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    update_compatible(PresetSelectCompatibleType::Always);
 | 
			
		||||
 | 
			
		||||
    /* set selected preset */
 | 
			
		||||
    for (size_t i = 0; i < filament_presets.size(); ++i)
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -1268,7 +1297,7 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
 | 
			
		|||
    this->filament_presets = { filaments.get_selected_preset_name() };
 | 
			
		||||
    for (unsigned int i = 1; i < 1000; ++ i) {
 | 
			
		||||
        char name[64];
 | 
			
		||||
        sprintf(name, "filament_%u", i);
 | 
			
		||||
        sprintf(name, "filament_%02u", i);
 | 
			
		||||
        if (! config.has("presets", name))
 | 
			
		||||
            break;
 | 
			
		||||
        this->filament_presets.emplace_back(remove_ini_suffix(config.get("presets", name)));
 | 
			
		||||
| 
						 | 
				
			
			@ -1276,8 +1305,9 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
 | 
			
		|||
    std::vector<std::string> filament_colors;
 | 
			
		||||
    if (config.has("presets", "filament_colors")) {
 | 
			
		||||
        boost::algorithm::split(filament_colors, config.get("presets", "filament_colors"), boost::algorithm::is_any_of(","));
 | 
			
		||||
        project_config.option<ConfigOptionStrings>("filament_colour")->values = filament_colors;
 | 
			
		||||
    }
 | 
			
		||||
    filament_colors.resize(filament_presets.size());
 | 
			
		||||
    project_config.option<ConfigOptionStrings>("filament_colour")->values = filament_colors;
 | 
			
		||||
    std::vector<std::string> matrix;
 | 
			
		||||
    if (config.has("presets", "flush_volumes_matrix")) {
 | 
			
		||||
        boost::algorithm::split(matrix, config.get("presets", "flush_volumes_matrix"), boost::algorithm::is_any_of("|"));
 | 
			
		||||
| 
						 | 
				
			
			@ -1340,13 +1370,14 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
 | 
			
		|||
void PresetBundle::export_selections(AppConfig &config)
 | 
			
		||||
{
 | 
			
		||||
	assert(this->printers.get_edited_preset().printer_technology() != ptFFF || filament_presets.size() >= 1);
 | 
			
		||||
//	assert(this->printers.get_edited_preset().printer_technology() != ptFFF || filament_presets.size() > 1 || filaments.get_selected_preset_name() == filament_presets.front());
 | 
			
		||||
	//assert(this->printers.get_edited_preset().printer_technology() != ptFFF || filament_presets.size() > 1 || filaments.get_selected_preset_name() == filament_presets.front());
 | 
			
		||||
    config.clear_section("presets");
 | 
			
		||||
    config.set("presets", PRESET_PRINT_NAME,        prints.get_selected_preset_name());
 | 
			
		||||
    config.set("presets", PRESET_FILAMENT_NAME,     filament_presets.front());
 | 
			
		||||
    for (unsigned i = 1; i < filament_presets.size(); ++i) {
 | 
			
		||||
        char name[64];
 | 
			
		||||
        sprintf(name, "filament_%u", i);
 | 
			
		||||
        assert(!filament_presets[i].empty());
 | 
			
		||||
        sprintf(name, "filament_%02u", i);
 | 
			
		||||
        config.set("presets", name, filament_presets[i]);
 | 
			
		||||
    }
 | 
			
		||||
    CNumericLocalesSetter locales_setter;
 | 
			
		||||
| 
						 | 
				
			
			@ -1402,6 +1433,13 @@ unsigned int PresetBundle::sync_ams_list(unsigned int &unknowns)
 | 
			
		|||
    for (auto &ams : filament_ams_list) {
 | 
			
		||||
        auto filament_id = ams.opt_string("filament_id", 0u);
 | 
			
		||||
        auto filament_color = ams.opt_string("filament_colour", 0u);
 | 
			
		||||
        auto filament_changed = !ams.has("filament_changed") || ams.opt_bool("filament_changed");
 | 
			
		||||
        if (filament_id.empty()) continue;
 | 
			
		||||
        if (!filament_changed && this->filament_presets.size() > filament_presets.size()) {
 | 
			
		||||
            filament_presets.push_back(this->filament_presets[filament_presets.size()]);
 | 
			
		||||
            filament_colors.push_back(filament_color);
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        auto iter = std::find_if(filaments.begin(), filaments.end(), [&filament_id](auto &f) { return f.is_compatible && f.is_system && f.filament_id == filament_id; });
 | 
			
		||||
        if (iter == filaments.end()) {
 | 
			
		||||
            BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": filament_id %1% not found or system or compatible") % filament_id;
 | 
			
		||||
| 
						 | 
				
			
			@ -3473,9 +3511,9 @@ std::vector<std::string> PresetBundle::export_current_configs(const std::string
 | 
			
		|||
        std::string file = path + "/" + preset->name + ".json";
 | 
			
		||||
        if (boost::filesystem::exists(file) && overwrite < 2) {
 | 
			
		||||
            overwrite = override_confirm(preset->name);
 | 
			
		||||
            if (overwrite == 0 || overwrite == 2)
 | 
			
		||||
                continue;
 | 
			
		||||
        }
 | 
			
		||||
        if (overwrite == 0 || overwrite == 2)
 | 
			
		||||
            continue;
 | 
			
		||||
        preset->config.save_to_json(file, preset->name, "", preset->version.to_string());
 | 
			
		||||
        result.push_back(file);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -47,10 +47,11 @@ public:
 | 
			
		|||
    void     load_selections(AppConfig &config, const PresetPreferences& preferred_selection = PresetPreferences());
 | 
			
		||||
 | 
			
		||||
    // BBS Load user presets
 | 
			
		||||
    PresetsConfigSubstitutions load_user_presets(std::string user, ForwardCompatibilitySubstitutionRule rule);
 | 
			
		||||
    PresetsConfigSubstitutions load_user_presets(AppConfig &config, std::map<std::string, std::map<std::string, std::string>>& my_presets, ForwardCompatibilitySubstitutionRule rule);
 | 
			
		||||
    PresetsConfigSubstitutions import_presets(std::vector<std::string> &files, std::function<int(std::string const &)> override_confirm, ForwardCompatibilitySubstitutionRule rule);
 | 
			
		||||
    void save_user_presets(AppConfig& config, std::vector<std::string>& need_to_delete_list);
 | 
			
		||||
    void remove_users_preset(AppConfig &config);
 | 
			
		||||
    void remove_users_preset(AppConfig &config, std::map<std::string, std::map<std::string, std::string>> * my_presets = nullptr);
 | 
			
		||||
    void update_user_presets_directory(const std::string preset_folder);
 | 
			
		||||
    void remove_user_presets_directory(const std::string preset_folder);
 | 
			
		||||
    void update_system_preset_setting_ids(std::map<std::string, std::map<std::string, std::string>>& system_presets);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -246,6 +246,9 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
 | 
			
		|||
            osteps.emplace_back(posSimplifyPath);
 | 
			
		||||
            osteps.emplace_back(posSimplifySupportPath);
 | 
			
		||||
            steps.emplace_back(psSkirtBrim);
 | 
			
		||||
        }
 | 
			
		||||
        else if (opt_key == "z_hop_types") {
 | 
			
		||||
            osteps.emplace_back(posDetectOverhangsForLift);
 | 
			
		||||
        } else {
 | 
			
		||||
            // for legacy, if we can't handle this option let's invalidate all steps
 | 
			
		||||
            //FIXME invalidate all steps of all objects as well?
 | 
			
		||||
| 
						 | 
				
			
			@ -354,10 +357,21 @@ std::vector<unsigned int> Print::support_material_extruders() const
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// returns 0-based indices of used extruders
 | 
			
		||||
std::vector<unsigned int> Print::extruders() const
 | 
			
		||||
std::vector<unsigned int> Print::extruders(bool conside_custom_gcode) const
 | 
			
		||||
{
 | 
			
		||||
    std::vector<unsigned int> extruders = this->object_extruders();
 | 
			
		||||
    append(extruders, this->support_material_extruders());
 | 
			
		||||
 | 
			
		||||
    if (conside_custom_gcode) {
 | 
			
		||||
        //BBS
 | 
			
		||||
        for (auto plate_data : m_model.plates_custom_gcodes) {
 | 
			
		||||
            for (auto item : plate_data.second.gcodes) {
 | 
			
		||||
                if (item.type == CustomGCode::Type::ToolChange)
 | 
			
		||||
                    extruders.push_back((unsigned int)item.extruder);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sort_remove_duplicates(extruders);
 | 
			
		||||
    return extruders;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1135,34 +1149,35 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    const ConfigOptionDef* bed_type_def = print_config_def.get("curr_bed_type");
 | 
			
		||||
    assert(bed_type_def != nullptr);
 | 
			
		||||
 | 
			
		||||
    if (is_BBL_printer()) {
 | 
			
		||||
        const t_config_enum_values* bed_type_keys_map = bed_type_def->enum_keys_map;
 | 
			
		||||
        for (unsigned int extruder_id : extruders) {
 | 
			
		||||
            const ConfigOptionInts* bed_temp_opt = m_config.option<ConfigOptionInts>(get_bed_temp_key(m_config.curr_bed_type));
 | 
			
		||||
            for (unsigned int extruder_id : extruders) {
 | 
			
		||||
                int curr_bed_temp = bed_temp_opt->get_at(extruder_id);
 | 
			
		||||
                if (curr_bed_temp == 0 && bed_type_keys_map != nullptr) {
 | 
			
		||||
                    std::string bed_type_name;
 | 
			
		||||
                    for (auto item : *bed_type_keys_map) {
 | 
			
		||||
                        if (item.second == m_config.curr_bed_type) {
 | 
			
		||||
                            bed_type_name = item.first;
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
	    if (is_BBL_printer()) {
 | 
			
		||||
	    const t_config_enum_values* bed_type_keys_map = bed_type_def->enum_keys_map;
 | 
			
		||||
	    for (unsigned int extruder_id : extruders) {
 | 
			
		||||
	        const ConfigOptionInts* bed_temp_opt = m_config.option<ConfigOptionInts>(get_bed_temp_key(m_config.curr_bed_type));
 | 
			
		||||
	        for (unsigned int extruder_id : extruders) {
 | 
			
		||||
	            int curr_bed_temp = bed_temp_opt->get_at(extruder_id);
 | 
			
		||||
	            if (curr_bed_temp == 0 && bed_type_keys_map != nullptr) {
 | 
			
		||||
	                std::string bed_type_name;
 | 
			
		||||
	                for (auto item : *bed_type_keys_map) {
 | 
			
		||||
	                    if (item.second == m_config.curr_bed_type) {
 | 
			
		||||
	                        bed_type_name = item.first;
 | 
			
		||||
	                        break;
 | 
			
		||||
	                    }
 | 
			
		||||
	                }
 | 
			
		||||
 | 
			
		||||
                    StringObjectException except;
 | 
			
		||||
                    except.string = format(L("Plate %d: %s does not support filament %s"), this->get_plate_index() + 1, L(bed_type_name), extruder_id + 1);
 | 
			
		||||
                    except.string += "\n";
 | 
			
		||||
                    except.type = STRING_EXCEPT_FILAMENT_NOT_MATCH_BED_TYPE;
 | 
			
		||||
                    except.params.push_back(std::to_string(this->get_plate_index() + 1));
 | 
			
		||||
                    except.params.push_back(L(bed_type_name));
 | 
			
		||||
                    except.params.push_back(std::to_string(extruder_id + 1));
 | 
			
		||||
                    except.object = nullptr;
 | 
			
		||||
                    return except;
 | 
			
		||||
                }
 | 
			
		||||
	                StringObjectException except;
 | 
			
		||||
	                except.string = format(L("Plate %d: %s does not support filament %s"), this->get_plate_index() + 1, L(bed_type_name), extruder_id + 1);
 | 
			
		||||
	                except.string += "\n";
 | 
			
		||||
	                except.type   = STRING_EXCEPT_FILAMENT_NOT_MATCH_BED_TYPE;
 | 
			
		||||
	                except.params.push_back(std::to_string(this->get_plate_index() + 1));
 | 
			
		||||
	                except.params.push_back(L(bed_type_name));
 | 
			
		||||
	                except.params.push_back(std::to_string(extruder_id+1));
 | 
			
		||||
	                except.object = nullptr;
 | 
			
		||||
	                return except;
 | 
			
		||||
	           }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -1307,7 +1322,6 @@ void  PrintObject::clear_shared_object()
 | 
			
		|||
        BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": this=%1%, clear previous shared object data %2%")%this %m_shared_object;
 | 
			
		||||
        m_layers.clear();
 | 
			
		||||
        m_support_layers.clear();
 | 
			
		||||
        m_tree_support_layers.clear();
 | 
			
		||||
 | 
			
		||||
        m_shared_object = nullptr;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1320,7 +1334,6 @@ void  PrintObject::copy_layers_from_shared_object()
 | 
			
		|||
    if (m_shared_object) {
 | 
			
		||||
        m_layers.clear();
 | 
			
		||||
        m_support_layers.clear();
 | 
			
		||||
        m_tree_support_layers.clear();
 | 
			
		||||
 | 
			
		||||
        firstLayerObjSliceByVolume.clear();
 | 
			
		||||
        firstLayerObjSliceByGroups.clear();
 | 
			
		||||
| 
						 | 
				
			
			@ -1328,7 +1341,6 @@ void  PrintObject::copy_layers_from_shared_object()
 | 
			
		|||
        BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": this=%1%, copied layers from object %2%")%this%m_shared_object;
 | 
			
		||||
        m_layers = m_shared_object->layers();
 | 
			
		||||
        m_support_layers = m_shared_object->support_layers();
 | 
			
		||||
        m_tree_support_layers = m_shared_object->tree_support_layers();
 | 
			
		||||
 | 
			
		||||
        firstLayerObjSliceByVolume = m_shared_object->firstLayerObjSlice();
 | 
			
		||||
        firstLayerObjSliceByGroups = m_shared_object->firstLayerObjGroups();
 | 
			
		||||
| 
						 | 
				
			
			@ -1660,6 +1672,17 @@ void Print::process(bool use_cache)
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // BBS
 | 
			
		||||
    for (PrintObject* obj : m_objects) {
 | 
			
		||||
        if (need_slicing_objects.count(obj) != 0) {
 | 
			
		||||
            obj->detect_overhangs_for_lift();
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            if (obj->set_started(posDetectOverhangsForLift))
 | 
			
		||||
                obj->set_done(posDetectOverhangsForLift);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1732,13 +1755,6 @@ void Print::_make_skirt()
 | 
			
		|||
                break;
 | 
			
		||||
            layer->support_fills.collect_points(object_points);
 | 
			
		||||
        }
 | 
			
		||||
        // BBS
 | 
			
		||||
        for (const TreeSupportLayer* layer : object->m_tree_support_layers) {
 | 
			
		||||
            if (layer->print_z > skirt_height_z)
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            layer->support_fills.collect_points(object_points);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        object_convex_hulls.insert({ object, Slic3r::Geometry::convex_hull(object_points) });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1885,12 +1901,12 @@ Polygons Print::first_layer_islands() const
 | 
			
		|||
        Polygons object_islands;
 | 
			
		||||
        for (ExPolygon &expoly : object->m_layers.front()->lslices)
 | 
			
		||||
            object_islands.push_back(expoly.contour);
 | 
			
		||||
        if (! object->support_layers().empty())
 | 
			
		||||
            object->support_layers().front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON));
 | 
			
		||||
        if (! object->tree_support_layers().empty()) {
 | 
			
		||||
            ExPolygons& expolys_first_layer = object->m_tree_support_layers.front()->lslices;
 | 
			
		||||
            for (ExPolygon &expoly : expolys_first_layer) {
 | 
			
		||||
                object_islands.push_back(expoly.contour);
 | 
			
		||||
        if (!object->support_layers().empty()) {
 | 
			
		||||
            if (object->support_layers().front()->support_type==stInnerNormal)
 | 
			
		||||
                object->support_layers().front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON));
 | 
			
		||||
            else if(object->support_layers().front()->support_type==stInnerTree) {
 | 
			
		||||
                ExPolygons &expolys_first_layer = object->m_support_layers.front()->lslices;
 | 
			
		||||
                for (ExPolygon &expoly : expolys_first_layer) { object_islands.push_back(expoly.contour); }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        islands.reserve(islands.size() + object_islands.size() * object->instances().size());
 | 
			
		||||
| 
						 | 
				
			
			@ -2241,7 +2257,7 @@ std::string PrintStatistics::finalize_output_path(const std::string &path_in) co
 | 
			
		|||
#define JSON_SUPPORT_LAYER_ISLANDS                  "support_islands"
 | 
			
		||||
#define JSON_SUPPORT_LAYER_FILLS                    "support_fills"
 | 
			
		||||
#define JSON_SUPPORT_LAYER_INTERFACE_ID             "interface_id"
 | 
			
		||||
 | 
			
		||||
#define JSON_SUPPORT_LAYER_TYPE                     "support_type"
 | 
			
		||||
 | 
			
		||||
#define JSON_LAYER_REGION_CONFIG_HASH             "config_hash"
 | 
			
		||||
#define JSON_LAYER_REGION_SLICES                  "slices"
 | 
			
		||||
| 
						 | 
				
			
			@ -2872,6 +2888,7 @@ void extract_layer(const json& layer_json, Layer& layer) {
 | 
			
		|||
void extract_support_layer(const json& support_layer_json, SupportLayer& support_layer) {
 | 
			
		||||
    extract_layer(support_layer_json, support_layer);
 | 
			
		||||
 | 
			
		||||
    support_layer.support_type = support_layer_json[JSON_SUPPORT_LAYER_TYPE];
 | 
			
		||||
    //support_islands
 | 
			
		||||
    int islands_count = support_layer_json[JSON_SUPPORT_LAYER_ISLANDS].size();
 | 
			
		||||
    for (int islands_index = 0; islands_index < islands_count; islands_index++)
 | 
			
		||||
| 
						 | 
				
			
			@ -2900,27 +2917,6 @@ void extract_support_layer(const json& support_layer_json, SupportLayer& support
 | 
			
		|||
    return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void extract_tree_support_layer(const json& tree_support_layer_json, TreeSupportLayer& tree_support_layer) {
 | 
			
		||||
    extract_layer(tree_support_layer_json, tree_support_layer);
 | 
			
		||||
 | 
			
		||||
    //support_fills
 | 
			
		||||
    tree_support_layer.support_fills.no_sort = tree_support_layer_json[JSON_SUPPORT_LAYER_FILLS][JSON_EXTRUSION_NO_SORT];
 | 
			
		||||
    int treesupport_fills_entities_count = tree_support_layer_json[JSON_SUPPORT_LAYER_FILLS][JSON_EXTRUSION_ENTITIES].size();
 | 
			
		||||
    for (int treesupport_fills_entities_index = 0; treesupport_fills_entities_index < treesupport_fills_entities_count; treesupport_fills_entities_index++)
 | 
			
		||||
    {
 | 
			
		||||
        const json& extrusion_entity_json =  tree_support_layer_json[JSON_SUPPORT_LAYER_FILLS][JSON_EXTRUSION_ENTITIES][treesupport_fills_entities_index];
 | 
			
		||||
        bool ret = convert_extrusion_from_json(extrusion_entity_json, tree_support_layer.support_fills);
 | 
			
		||||
        if (!ret) {
 | 
			
		||||
            BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": error parsing fills found at tree_support_layer %1%, print_z %2%")%tree_support_layer.id() %tree_support_layer.print_z;
 | 
			
		||||
            char error_buf[1024];
 | 
			
		||||
            ::sprintf(error_buf, "Error while parsing fills at tree_support_layer %d, print_z %f", tree_support_layer.id(), tree_support_layer.print_z);
 | 
			
		||||
            throw Slic3r::FileIOError(error_buf);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int Print::export_cached_data(const std::string& directory, bool with_space)
 | 
			
		||||
{
 | 
			
		||||
    int ret = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -2986,7 +2982,7 @@ int Print::export_cached_data(const std::string& directory, bool with_space)
 | 
			
		|||
        std::string file_name = directory +"/obj_"+std::to_string(arrange_order)+".json";
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            json root_json, layers_json = json::array(), support_layers_json = json::array(), tree_support_layers_json = json::array();
 | 
			
		||||
            json root_json, layers_json = json::array(), support_layers_json = json::array();
 | 
			
		||||
 | 
			
		||||
            root_json[JSON_OBJECT_NAME] = model_obj->name;
 | 
			
		||||
            root_json[JSON_ARRANGE_ORDER] = arrange_order;
 | 
			
		||||
| 
						 | 
				
			
			@ -3031,6 +3027,7 @@ int Print::export_cached_data(const std::string& directory, bool with_space)
 | 
			
		|||
                        convert_layer_to_json(support_layer_json, support_layer);
 | 
			
		||||
 | 
			
		||||
                        support_layer_json[JSON_SUPPORT_LAYER_INTERFACE_ID] = support_layer->interface_id();
 | 
			
		||||
                        support_layer_json[JSON_SUPPORT_LAYER_TYPE] = support_layer->support_type;
 | 
			
		||||
 | 
			
		||||
                        //support_islands
 | 
			
		||||
                        for (const ExPolygon& support_island : support_layer->support_islands.expolygons) {
 | 
			
		||||
| 
						 | 
				
			
			@ -3094,136 +3091,6 @@ int Print::export_cached_data(const std::string& directory, bool with_space)
 | 
			
		|||
            } // for each layer*/
 | 
			
		||||
            root_json[JSON_SUPPORT_LAYERS] = std::move(support_layers_json);
 | 
			
		||||
 | 
			
		||||
            //export the tree support layers
 | 
			
		||||
            std::vector<json> tree_support_layers_json_vector(obj->tree_support_layer_count());
 | 
			
		||||
            tbb::parallel_for(
 | 
			
		||||
                tbb::blocked_range<size_t>(0, obj->tree_support_layer_count()),
 | 
			
		||||
                [&tree_support_layers_json_vector, obj, convert_layer_to_json](const tbb::blocked_range<size_t>& tree_support_layer_range) {
 | 
			
		||||
                    for (size_t ts_layer_index = tree_support_layer_range.begin(); ts_layer_index < tree_support_layer_range.end(); ++ ts_layer_index) {
 | 
			
		||||
                        const TreeSupportLayer *tree_support_layer = obj->get_tree_support_layer(ts_layer_index);
 | 
			
		||||
                        json treesupport_layer_json, treesupport_fills_json, treesupportfills_entities_json = json::array();
 | 
			
		||||
                        //json overhang_areas_json = json::array(), roof_areas_json = json::array(), roof_1st_layer_json = json::array(), floor_areas_json = json::array(), base_areas_json = json::array();
 | 
			
		||||
 | 
			
		||||
                        convert_layer_to_json(treesupport_layer_json, tree_support_layer);
 | 
			
		||||
 | 
			
		||||
                        //tree_support_fills
 | 
			
		||||
                        treesupport_fills_json[JSON_EXTRUSION_NO_SORT] = tree_support_layer->support_fills.no_sort;
 | 
			
		||||
                        treesupport_fills_json[JSON_EXTRUSION_ENTITY_TYPE] = JSON_EXTRUSION_TYPE_COLLECTION;
 | 
			
		||||
                        for (const ExtrusionEntity* extrusion_entity : tree_support_layer->support_fills.entities) {
 | 
			
		||||
                            json treesupportfill_entity_json, treesupportfill_entity_paths_json = json::array();
 | 
			
		||||
                            bool ret = convert_extrusion_to_json(treesupportfill_entity_json, treesupportfill_entity_paths_json, extrusion_entity);
 | 
			
		||||
                            if (!ret)
 | 
			
		||||
                                continue;
 | 
			
		||||
 | 
			
		||||
                            treesupportfills_entities_json.push_back(std::move(treesupportfill_entity_json));
 | 
			
		||||
                        }
 | 
			
		||||
                        treesupport_fills_json[JSON_EXTRUSION_ENTITIES] = std::move(treesupportfills_entities_json);
 | 
			
		||||
                        treesupport_layer_json[JSON_SUPPORT_LAYER_FILLS] = std::move(treesupport_fills_json);
 | 
			
		||||
 | 
			
		||||
                        //following data are not needed in the later stage
 | 
			
		||||
                        //overhang_areas
 | 
			
		||||
                        /*for (const ExPolygon& overhang_area : tree_support_layer->overhang_areas) {
 | 
			
		||||
                            json overhang_area_json = overhang_area;
 | 
			
		||||
                            overhang_areas_json.push_back(std::move(overhang_area_json));
 | 
			
		||||
                        }
 | 
			
		||||
                        treesupport_layer_json["overhang_areas"] = std::move(overhang_areas_json);
 | 
			
		||||
 | 
			
		||||
                         //roof_areas
 | 
			
		||||
                        for (const ExPolygon& roof_area : tree_support_layer->roof_areas) {
 | 
			
		||||
                            json roof_area_json = roof_area;
 | 
			
		||||
                            roof_areas_json.push_back(std::move(roof_area_json));
 | 
			
		||||
                        }
 | 
			
		||||
                        treesupport_layer_json["roof_areas"] = std::move(roof_areas_json);
 | 
			
		||||
 | 
			
		||||
                         //roof_1st_layer
 | 
			
		||||
                        for (const ExPolygon& layer_poly : tree_support_layer->roof_1st_layer) {
 | 
			
		||||
                            json layer_poly_json = layer_poly;
 | 
			
		||||
                            roof_1st_layer_json.push_back(std::move(layer_poly_json));
 | 
			
		||||
                        }
 | 
			
		||||
                        treesupport_layer_json["roof_1st_layer"] = std::move(roof_1st_layer_json);
 | 
			
		||||
 | 
			
		||||
                         //floor_areas
 | 
			
		||||
                        for (const ExPolygon& floor_area : tree_support_layer->floor_areas) {
 | 
			
		||||
                            json floor_area_json = floor_area;
 | 
			
		||||
                            floor_areas_json.push_back(std::move(floor_area_json));
 | 
			
		||||
                        }
 | 
			
		||||
                        treesupport_layer_json["floor_areas"] = std::move(floor_areas_json);
 | 
			
		||||
 | 
			
		||||
                         //base_areas
 | 
			
		||||
                        for (const ExPolygon& base_area : tree_support_layer->base_areas) {
 | 
			
		||||
                            json base_area_json = base_area;
 | 
			
		||||
                            base_areas_json.push_back(std::move(base_area_json));
 | 
			
		||||
                        }
 | 
			
		||||
                        treesupport_layer_json["base_areas"] = std::move(base_areas_json);*/
 | 
			
		||||
 | 
			
		||||
                        tree_support_layers_json_vector[ts_layer_index] = std::move(treesupport_layer_json);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            );
 | 
			
		||||
            for (int ts_index = 0; ts_index < tree_support_layers_json_vector.size(); ts_index++) {
 | 
			
		||||
                tree_support_layers_json.push_back(std::move(tree_support_layers_json_vector[ts_index]));
 | 
			
		||||
            }
 | 
			
		||||
            tree_support_layers_json_vector.clear();
 | 
			
		||||
#if 0
 | 
			
		||||
            for (const TreeSupportLayer *tree_support_layer : obj->tree_support_layers()) {
 | 
			
		||||
                json treesupport_layer_json, treesupport_fills_json, treesupportfills_entities_json = json::array();
 | 
			
		||||
                json overhang_areas_json = json::array(), roof_areas_json = json::array(), roof_1st_layer_json = json::array(), floor_areas_json = json::array(), base_areas_json = json::array();
 | 
			
		||||
 | 
			
		||||
                convert_layer_to_json(treesupport_layer_json, tree_support_layer);
 | 
			
		||||
 | 
			
		||||
                //tree_support_fills
 | 
			
		||||
                treesupport_fills_json[JSON_EXTRUSION_NO_SORT] = tree_support_layer->support_fills.no_sort;
 | 
			
		||||
                treesupport_fills_json[JSON_EXTRUSION_ENTITY_TYPE] = JSON_EXTRUSION_TYPE_COLLECTION;
 | 
			
		||||
                for (const ExtrusionEntity* extrusion_entity : tree_support_layer->support_fills.entities) {
 | 
			
		||||
                    json treesupportfill_entity_json, treesupportfill_entity_paths_json = json::array();
 | 
			
		||||
                    bool ret = convert_extrusion_to_json(treesupportfill_entity_json, treesupportfill_entity_paths_json, extrusion_entity);
 | 
			
		||||
                    if (!ret)
 | 
			
		||||
                        continue;
 | 
			
		||||
 | 
			
		||||
                    treesupportfills_entities_json.push_back(std::move(treesupportfill_entity_json));
 | 
			
		||||
                }
 | 
			
		||||
                treesupport_fills_json[JSON_EXTRUSION_ENTITIES] = std::move(treesupportfills_entities_json);
 | 
			
		||||
                treesupport_layer_json[JSON_SUPPORT_LAYER_FILLS] = std::move(treesupport_fills_json);
 | 
			
		||||
 | 
			
		||||
                //overhang_areas
 | 
			
		||||
                /*for (const ExPolygon& overhang_area : tree_support_layer->overhang_areas) {
 | 
			
		||||
                    json overhang_area_json = overhang_area;
 | 
			
		||||
                    overhang_areas_json.push_back(std::move(overhang_area_json));
 | 
			
		||||
                }
 | 
			
		||||
                treesupport_layer_json["overhang_areas"] = std::move(overhang_areas_json);
 | 
			
		||||
 | 
			
		||||
                 //roof_areas
 | 
			
		||||
                for (const ExPolygon& roof_area : tree_support_layer->roof_areas) {
 | 
			
		||||
                    json roof_area_json = roof_area;
 | 
			
		||||
                    roof_areas_json.push_back(std::move(roof_area_json));
 | 
			
		||||
                }
 | 
			
		||||
                treesupport_layer_json["roof_areas"] = std::move(roof_areas_json);
 | 
			
		||||
 | 
			
		||||
                 //roof_1st_layer
 | 
			
		||||
                for (const ExPolygon& layer_poly : tree_support_layer->roof_1st_layer) {
 | 
			
		||||
                    json layer_poly_json = layer_poly;
 | 
			
		||||
                    roof_1st_layer_json.push_back(std::move(layer_poly_json));
 | 
			
		||||
                }
 | 
			
		||||
                treesupport_layer_json["roof_1st_layer"] = std::move(roof_1st_layer_json);
 | 
			
		||||
 | 
			
		||||
                 //floor_areas
 | 
			
		||||
                for (const ExPolygon& floor_area : tree_support_layer->floor_areas) {
 | 
			
		||||
                    json floor_area_json = floor_area;
 | 
			
		||||
                    floor_areas_json.push_back(std::move(floor_area_json));
 | 
			
		||||
                }
 | 
			
		||||
                treesupport_layer_json["floor_areas"] = std::move(floor_areas_json);
 | 
			
		||||
 | 
			
		||||
                 //base_areas
 | 
			
		||||
                for (const ExPolygon& base_area : tree_support_layer->base_areas) {
 | 
			
		||||
                    json base_area_json = base_area;
 | 
			
		||||
                    base_areas_json.push_back(std::move(base_area_json));
 | 
			
		||||
                }
 | 
			
		||||
                treesupport_layer_json["base_areas"] = std::move(base_areas_json);*/
 | 
			
		||||
 | 
			
		||||
                tree_support_layers_json.push_back(std::move(treesupport_layer_json));
 | 
			
		||||
            } // for each layer
 | 
			
		||||
#endif
 | 
			
		||||
            root_json[JSON_TREE_SUPPORT_LAYERS] = std::move(tree_support_layers_json);
 | 
			
		||||
 | 
			
		||||
            filename_vector.push_back(file_name);
 | 
			
		||||
            json_vector.push_back(std::move(root_json));
 | 
			
		||||
| 
						 | 
				
			
			@ -3302,7 +3169,6 @@ int Print::load_cached_data(const std::string& directory)
 | 
			
		|||
 | 
			
		||||
        obj->clear_layers();
 | 
			
		||||
        obj->clear_support_layers();
 | 
			
		||||
        obj->clear_tree_support_layers();
 | 
			
		||||
 | 
			
		||||
        int arrange_order = model_instance->arrange_order;
 | 
			
		||||
        if (arrange_order <= 0) {
 | 
			
		||||
| 
						 | 
				
			
			@ -3354,13 +3220,12 @@ int Print::load_cached_data(const std::string& directory)
 | 
			
		|||
 | 
			
		||||
            std::string name = root_json.at(JSON_OBJECT_NAME);
 | 
			
		||||
            int order = root_json.at(JSON_ARRANGE_ORDER);
 | 
			
		||||
            int layer_count = 0, support_layer_count = 0, treesupport_layer_count = 0;
 | 
			
		||||
            int layer_count = 0, support_layer_count = 0;
 | 
			
		||||
 | 
			
		||||
            layer_count = root_json[JSON_LAYERS].size();
 | 
			
		||||
            support_layer_count = root_json[JSON_SUPPORT_LAYERS].size();
 | 
			
		||||
            treesupport_layer_count = root_json[JSON_TREE_SUPPORT_LAYERS].size();
 | 
			
		||||
 | 
			
		||||
            BOOST_LOG_TRIVIAL(info) << __FUNCTION__<<boost::format(":will load %1%, arrange_order %2%, layer_count %3%, support_layer_count %4%, treesupport_layer_count %5%")%name %order %layer_count %support_layer_count %treesupport_layer_count;
 | 
			
		||||
            BOOST_LOG_TRIVIAL(info) << __FUNCTION__<<boost::format(":will load %1%, arrange_order %2%, layer_count %3%, support_layer_count %4%")%name %order %layer_count %support_layer_count;
 | 
			
		||||
 | 
			
		||||
            Layer* previous_layer = NULL;
 | 
			
		||||
            //create layer and layer regions
 | 
			
		||||
| 
						 | 
				
			
			@ -3441,35 +3306,6 @@ int Print::load_cached_data(const std::string& directory)
 | 
			
		|||
                }
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            //tree support layers
 | 
			
		||||
            Layer* previous_tree_support_layer = NULL;
 | 
			
		||||
            //create tree_support_layers
 | 
			
		||||
            for (int index = 0; index < treesupport_layer_count; index++)
 | 
			
		||||
            {
 | 
			
		||||
                json& layer_json = root_json[JSON_TREE_SUPPORT_LAYERS][index];
 | 
			
		||||
                TreeSupportLayer* new_tree_support_layer = obj->add_tree_support_layer(layer_json[JSON_LAYER_ID], layer_json[JSON_LAYER_HEIGHT], layer_json[JSON_LAYER_PRINT_Z], layer_json[JSON_LAYER_SLICE_Z]);
 | 
			
		||||
                if (!new_tree_support_layer) {
 | 
			
		||||
                    BOOST_LOG_TRIVIAL(error) <<__FUNCTION__<< boost::format(":add_support_layer failed, out of memory");
 | 
			
		||||
                    return CLI_OUT_OF_MEMORY;
 | 
			
		||||
                }
 | 
			
		||||
                if (previous_tree_support_layer) {
 | 
			
		||||
                    previous_tree_support_layer->upper_layer = new_tree_support_layer;
 | 
			
		||||
                    new_tree_support_layer->lower_layer = previous_tree_support_layer;
 | 
			
		||||
                }
 | 
			
		||||
                previous_tree_support_layer = new_tree_support_layer;
 | 
			
		||||
            }
 | 
			
		||||
            BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": finished load support_layers, start to load treesupport_layers.");
 | 
			
		||||
            tbb::parallel_for(
 | 
			
		||||
                tbb::blocked_range<size_t>(0, obj->tree_support_layer_count()),
 | 
			
		||||
                [&root_json, &obj](const tbb::blocked_range<size_t>& tree_support_layer_range) {
 | 
			
		||||
                    for (size_t layer_index = tree_support_layer_range.begin(); layer_index < tree_support_layer_range.end(); ++ layer_index) {
 | 
			
		||||
                        const json& layer_json = root_json[JSON_TREE_SUPPORT_LAYERS][layer_index];
 | 
			
		||||
                        TreeSupportLayer* tree_support_layer = obj->get_tree_support_layer(layer_index);
 | 
			
		||||
                        extract_tree_support_layer(layer_json, *tree_support_layer);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            count ++;
 | 
			
		||||
            BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": load object %1% from %2% successfully.")%count%object_filenames[obj_index].first;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -3489,4 +3325,4 @@ int Print::load_cached_data(const std::string& directory)
 | 
			
		|||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
| 
						 | 
				
			
			@ -32,7 +32,6 @@ class Print;
 | 
			
		|||
class PrintObject;
 | 
			
		||||
class SupportLayer;
 | 
			
		||||
// BBS
 | 
			
		||||
class TreeSupportLayer;
 | 
			
		||||
class TreeSupportData;
 | 
			
		||||
class TreeSupport;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -86,7 +85,10 @@ enum PrintStep {
 | 
			
		|||
 | 
			
		||||
enum PrintObjectStep {
 | 
			
		||||
    posSlice, posPerimeters, posPrepareInfill,
 | 
			
		||||
    posInfill, posIroning, posSupportMaterial, posSimplifyPath, posSimplifySupportPath, posCount,
 | 
			
		||||
    posInfill, posIroning, posSupportMaterial, posSimplifyPath, posSimplifySupportPath,
 | 
			
		||||
    // BBS
 | 
			
		||||
    posDetectOverhangsForLift,
 | 
			
		||||
    posCount,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// A PrintRegion object represents a group of volumes to print
 | 
			
		||||
| 
						 | 
				
			
			@ -176,13 +178,6 @@ class ConstSupportLayerPtrsAdaptor : public ConstVectorOfPtrsAdaptor<SupportLaye
 | 
			
		|||
    ConstSupportLayerPtrsAdaptor(const SupportLayerPtrs *data) : ConstVectorOfPtrsAdaptor<SupportLayer>(data) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// BBS
 | 
			
		||||
typedef std::vector<TreeSupportLayer*>        TreeSupportLayerPtrs;
 | 
			
		||||
class ConstTreeSupportLayerPtrsAdaptor : public ConstVectorOfPtrsAdaptor<TreeSupportLayer> {
 | 
			
		||||
    friend PrintObject;
 | 
			
		||||
    ConstTreeSupportLayerPtrsAdaptor(const TreeSupportLayerPtrs* data) : ConstVectorOfPtrsAdaptor<TreeSupportLayer>(data) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class BoundingBoxf3;        // TODO: for temporary constructor parameter
 | 
			
		||||
 | 
			
		||||
// Single instance of a PrintObject.
 | 
			
		||||
| 
						 | 
				
			
			@ -297,14 +292,10 @@ public:
 | 
			
		|||
    Transform3d                  trafo_centered() const
 | 
			
		||||
        { Transform3d t = this->trafo(); t.pretranslate(Vec3d(- unscale<double>(m_center_offset.x()), - unscale<double>(m_center_offset.y()), 0)); return t; }
 | 
			
		||||
    const PrintInstances&        instances() const      { return m_instances; }
 | 
			
		||||
    // BBS
 | 
			
		||||
    ConstTreeSupportLayerPtrsAdaptor tree_support_layers() const { return ConstTreeSupportLayerPtrsAdaptor(&m_tree_support_layers); }
 | 
			
		||||
 | 
			
		||||
    // Whoever will get a non-const pointer to PrintObject will be able to modify its layers.
 | 
			
		||||
    LayerPtrs&                   layers()               { return m_layers; }
 | 
			
		||||
    SupportLayerPtrs&            support_layers()       { return m_support_layers; }
 | 
			
		||||
    // BBS
 | 
			
		||||
    TreeSupportLayerPtrs&        tree_support_layers() { return m_tree_support_layers; }
 | 
			
		||||
 | 
			
		||||
    template<typename PolysType>
 | 
			
		||||
    static void remove_bridges_from_contacts(
 | 
			
		||||
| 
						 | 
				
			
			@ -327,7 +318,9 @@ public:
 | 
			
		|||
    // BBS
 | 
			
		||||
    void generate_support_preview();
 | 
			
		||||
    const std::vector<VolumeSlices>& firstLayerObjSlice() const { return firstLayerObjSliceByVolume; }
 | 
			
		||||
    std::vector<VolumeSlices>& firstLayerObjSliceMod() { return firstLayerObjSliceByVolume; }
 | 
			
		||||
    const std::vector<groupedVolumeSlices>& firstLayerObjGroups() const { return firstLayerObjSliceByGroups; }
 | 
			
		||||
    std::vector<groupedVolumeSlices>& firstLayerObjGroupsMod() { return firstLayerObjSliceByGroups; }
 | 
			
		||||
 | 
			
		||||
    bool                         has_brim() const       {
 | 
			
		||||
        return ((this->config().brim_type != btNoBrim && this->config().brim_width.value > 0.) || this->config().brim_type == btAutoBrim)
 | 
			
		||||
| 
						 | 
				
			
			@ -365,12 +358,7 @@ public:
 | 
			
		|||
    Layer*          add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z);
 | 
			
		||||
 | 
			
		||||
    // BBS
 | 
			
		||||
    TreeSupportLayer* get_tree_support_layer(int idx) { return m_tree_support_layers[idx]; }
 | 
			
		||||
    const TreeSupportLayer* get_tree_support_layer_at_printz(coordf_t print_z, coordf_t epsilon) const;
 | 
			
		||||
    TreeSupportLayer* get_tree_support_layer_at_printz(coordf_t print_z, coordf_t epsilon);
 | 
			
		||||
    TreeSupportLayer* add_tree_support_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z);
 | 
			
		||||
    void  clear_tree_support_layers();
 | 
			
		||||
    size_t tree_support_layer_count() const { return m_tree_support_layers.size(); }
 | 
			
		||||
    SupportLayer* add_tree_support_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z);
 | 
			
		||||
    std::shared_ptr<TreeSupportData> alloc_tree_support_preview_cache();
 | 
			
		||||
    void clear_tree_support_preview_cache() { m_tree_support_preview_cache.reset(); }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -381,7 +369,6 @@ public:
 | 
			
		|||
    SupportLayer*   get_support_layer_at_printz(coordf_t print_z, coordf_t epsilon);
 | 
			
		||||
    SupportLayer*   add_support_layer(int id, int interface_id, coordf_t height, coordf_t print_z);
 | 
			
		||||
    SupportLayerPtrs::iterator insert_support_layer(SupportLayerPtrs::iterator pos, size_t id, size_t interface_id, coordf_t height, coordf_t print_z, coordf_t slice_z);
 | 
			
		||||
    void            delete_support_layer(int idx);
 | 
			
		||||
 | 
			
		||||
    // Initialize the layer_height_profile from the model_object's layer_height_profile, from model_object's layer height table, or from slicing parameters.
 | 
			
		||||
    // Returns true, if the layer_height_profile was changed.
 | 
			
		||||
| 
						 | 
				
			
			@ -464,6 +451,10 @@ private:
 | 
			
		|||
    void slice_volumes();
 | 
			
		||||
    //BBS
 | 
			
		||||
    ExPolygons _shrink_contour_holes(double contour_delta, double hole_delta, const ExPolygons& polys) const;
 | 
			
		||||
    // BBS
 | 
			
		||||
    void detect_overhangs_for_lift();
 | 
			
		||||
    void clear_overhangs_for_lift();
 | 
			
		||||
 | 
			
		||||
    // Has any support (not counting the raft).
 | 
			
		||||
    void detect_surfaces_type();
 | 
			
		||||
    void process_external_surfaces();
 | 
			
		||||
| 
						 | 
				
			
			@ -498,7 +489,6 @@ private:
 | 
			
		|||
    LayerPtrs                               m_layers;
 | 
			
		||||
    SupportLayerPtrs                        m_support_layers;
 | 
			
		||||
    // BBS
 | 
			
		||||
    TreeSupportLayerPtrs                    m_tree_support_layers;
 | 
			
		||||
    std::shared_ptr<TreeSupportData>        m_tree_support_preview_cache;
 | 
			
		||||
 | 
			
		||||
    // this is set to true when LayerRegion->slices is split in top/internal/bottom
 | 
			
		||||
| 
						 | 
				
			
			@ -661,7 +651,7 @@ public:
 | 
			
		|||
 | 
			
		||||
    std::vector<unsigned int> object_extruders() const;
 | 
			
		||||
    std::vector<unsigned int> support_material_extruders() const;
 | 
			
		||||
    std::vector<unsigned int> extruders() const;
 | 
			
		||||
    std::vector<unsigned int> extruders(bool conside_custom_gcode = false) const;
 | 
			
		||||
    double              max_allowed_layer_height() const;
 | 
			
		||||
    bool                has_support_material() const;
 | 
			
		||||
    // Make sure the background processing has no access to this model_object during this call!
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1020,10 +1020,11 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
 | 
			
		|||
	new_full_config.option("filament_settings_id",         true);
 | 
			
		||||
	new_full_config.option("printer_settings_id",          true);
 | 
			
		||||
    // BBS
 | 
			
		||||
    int used_filaments = this->extruders().size();
 | 
			
		||||
    int used_filaments = this->extruders(true).size();
 | 
			
		||||
 | 
			
		||||
    //new_full_config.normalize_fdm(used_filaments);
 | 
			
		||||
    new_full_config.normalize_fdm_1();
 | 
			
		||||
    t_config_option_keys changed_keys = new_full_config.normalize_fdm_2(used_filaments);
 | 
			
		||||
    t_config_option_keys changed_keys = new_full_config.normalize_fdm_2(objects().size(), used_filaments);
 | 
			
		||||
    if (changed_keys.size() > 0) {
 | 
			
		||||
        BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", got changed_keys, size=%1%")%changed_keys.size();
 | 
			
		||||
        for (int i = 0; i < changed_keys.size(); i++)
 | 
			
		||||
| 
						 | 
				
			
			@ -1117,17 +1118,19 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
 | 
			
		|||
		for (const ModelObject *model_object : m_model.objects)
 | 
			
		||||
			model_object_status_db.add(*model_object, ModelObjectStatus::New);
 | 
			
		||||
    } else {
 | 
			
		||||
        if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) {
 | 
			
		||||
            update_apply_status(num_extruders_changed  ||
 | 
			
		||||
            	// Tool change G-codes are applied as color changes for a single extruder printer, no need to invalidate tool ordering.
 | 
			
		||||
            	//FIXME The tool ordering may be invalidated unnecessarily if the custom_gcode_per_print_z.mode is not applicable
 | 
			
		||||
            	// to the active print / model state, and then it is reset, so it is being applicable, but empty, thus the effect is the same.
 | 
			
		||||
            	(num_extruders  > 1 && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes)) ?
 | 
			
		||||
            	// The Tool Ordering and the Wipe Tower are no more valid.
 | 
			
		||||
            	this->invalidate_steps({ psWipeTower, psGCodeExport }) :
 | 
			
		||||
            	// There is no change in Tool Changes stored in custom_gcode_per_print_z, therefore there is no need to update Tool Ordering.
 | 
			
		||||
            	this->invalidate_step(psGCodeExport));
 | 
			
		||||
            m_model.custom_gcode_per_print_z = model.custom_gcode_per_print_z;
 | 
			
		||||
        //BBS: replace model custom gcode with current plate custom gcode
 | 
			
		||||
        m_model.curr_plate_index = model.curr_plate_index;
 | 
			
		||||
        if (m_model.get_curr_plate_custom_gcodes() != model.get_curr_plate_custom_gcodes()) {
 | 
			
		||||
            update_apply_status(num_extruders_changed ||
 | 
			
		||||
                // Tool change G-codes are applied as color changes for a single extruder printer, no need to invalidate tool ordering.
 | 
			
		||||
                //FIXME The tool ordering may be invalidated unnecessarily if the custom_gcode_per_print_z.mode is not applicable
 | 
			
		||||
                // to the active print / model state, and then it is reset, so it is being applicable, but empty, thus the effect is the same.
 | 
			
		||||
                (num_extruders > 1 && custom_per_printz_gcodes_tool_changes_differ(m_model.get_curr_plate_custom_gcodes().gcodes, model.get_curr_plate_custom_gcodes().gcodes)) ?
 | 
			
		||||
                // The Tool Ordering and the Wipe Tower are no more valid.
 | 
			
		||||
                this->invalidate_steps({ psWipeTower, psGCodeExport }) :
 | 
			
		||||
                // There is no change in Tool Changes stored in custom_gcode_per_print_z, therefore there is no need to update Tool Ordering.
 | 
			
		||||
                this->invalidate_step(psGCodeExport));
 | 
			
		||||
            m_model.plates_custom_gcodes[m_model.curr_plate_index] = model.get_curr_plate_custom_gcodes();
 | 
			
		||||
        }
 | 
			
		||||
        if (model_object_list_equal(m_model, model)) {
 | 
			
		||||
            // The object list did not change.
 | 
			
		||||
| 
						 | 
				
			
			@ -1413,8 +1416,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    //BBS: check the config again
 | 
			
		||||
    int new_used_filaments = this->extruders().size();
 | 
			
		||||
    t_config_option_keys new_changed_keys = new_full_config.normalize_fdm_2(new_used_filaments);
 | 
			
		||||
    int new_used_filaments = this->extruders(true).size();
 | 
			
		||||
    t_config_option_keys new_changed_keys = new_full_config.normalize_fdm_2(objects().size(), new_used_filaments);
 | 
			
		||||
    if (new_changed_keys.size() > 0) {
 | 
			
		||||
        BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", got new_changed_keys, size=%1%")%new_changed_keys.size();
 | 
			
		||||
        for (int i = 0; i < new_changed_keys.size(); i++)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -159,7 +159,8 @@ static t_config_enum_values s_keys_map_WallInfillOrder {
 | 
			
		|||
    { "outer wall/inner wall/infill",     int(WallInfillOrder::OuterInnerInfill) },
 | 
			
		||||
    { "inner-outer-inner wall/infill",     int(WallInfillOrder::InnerOuterInnerInfill) },
 | 
			
		||||
    { "infill/inner wall/outer wall",     int(WallInfillOrder::InfillInnerOuter) },
 | 
			
		||||
    { "infill/outer wall/inner wall",     int(WallInfillOrder::InfillOuterInner) }
 | 
			
		||||
    { "infill/outer wall/inner wall",     int(WallInfillOrder::InfillOuterInner) },
 | 
			
		||||
    { "inner-outer-inner wall/infill",     int(WallInfillOrder::InnerOuterInnerInfill)}
 | 
			
		||||
};
 | 
			
		||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(WallInfillOrder)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -303,6 +304,14 @@ static t_config_enum_values s_keys_map_PerimeterGeneratorType{
 | 
			
		|||
};
 | 
			
		||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PerimeterGeneratorType)
 | 
			
		||||
 | 
			
		||||
static const t_config_enum_values s_keys_map_ZHopType = {
 | 
			
		||||
    { "Auto Lift",          zhtAuto },
 | 
			
		||||
    { "Normal Lift",        zhtNormal },
 | 
			
		||||
    { "Slope Lift",         zhtSlope },
 | 
			
		||||
    { "Spiral Lift",        zhtSpiral }
 | 
			
		||||
};
 | 
			
		||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ZHopType)
 | 
			
		||||
 | 
			
		||||
static void assign_printer_technology_to_unknown(t_optiondef_map &options, PrinterTechnology printer_technology)
 | 
			
		||||
{
 | 
			
		||||
    for (std::pair<const t_config_option_key, ConfigOptionDef> &kvp : options)
 | 
			
		||||
| 
						 | 
				
			
			@ -813,8 +822,8 @@ void PrintConfigDef::init_fff_params()
 | 
			
		|||
    def = this->add("brim_type", coEnum);
 | 
			
		||||
    def->label = L("Brim type");
 | 
			
		||||
    def->category = L("Support");
 | 
			
		||||
    def->tooltip = L("This controls brim position including outer side of models, inner side of holes or both. "
 | 
			
		||||
                     "Auto means both the brim position and brim width is analysed and calculated automatically");
 | 
			
		||||
    def->tooltip = L("This controls the generation of the brim at outer side of models. "
 | 
			
		||||
                     "Auto means the brim width is analysed and calculated automatically.");
 | 
			
		||||
    def->enum_keys_map = &ConfigOptionEnum<BrimType>::get_enum_values();
 | 
			
		||||
    def->enum_values.emplace_back("auto_brim");
 | 
			
		||||
    def->enum_values.emplace_back("outer_only");
 | 
			
		||||
| 
						 | 
				
			
			@ -1325,6 +1334,7 @@ void PrintConfigDef::init_fff_params()
 | 
			
		|||
    def->enum_values.push_back("PA-CF");
 | 
			
		||||
    def->enum_values.push_back("PLA-CF");
 | 
			
		||||
    def->enum_values.push_back("PET-CF");
 | 
			
		||||
    def->enum_values.push_back("PETG-CF");
 | 
			
		||||
    def->enum_values.push_back("PVA");
 | 
			
		||||
    def->mode = comSimple;
 | 
			
		||||
    def->set_default_value(new ConfigOptionStrings { "PLA" });
 | 
			
		||||
| 
						 | 
				
			
			@ -2149,6 +2159,7 @@ void PrintConfigDef::init_fff_params()
 | 
			
		|||
    def->tooltip = L("Diameter of nozzle");
 | 
			
		||||
    def->sidetext = L("mm");
 | 
			
		||||
    def->mode = comAdvanced;
 | 
			
		||||
    def->max = 1.0;
 | 
			
		||||
    def->set_default_value(new ConfigOptionFloats { 0.4 });
 | 
			
		||||
 | 
			
		||||
    def = this->add("host_type", coEnum);
 | 
			
		||||
| 
						 | 
				
			
			@ -2183,6 +2194,14 @@ void PrintConfigDef::init_fff_params()
 | 
			
		|||
    def->readonly = true;
 | 
			
		||||
    def->set_default_value(new ConfigOptionFloat { 0.0 });
 | 
			
		||||
 | 
			
		||||
    def = this->add("start_end_points", coPoints);
 | 
			
		||||
    def->label = L("Start end points");
 | 
			
		||||
    def->tooltip  = L("The start and end points which is from cutter area to garbage can.");
 | 
			
		||||
    def->mode     = comDevelop;
 | 
			
		||||
    def->readonly = true;
 | 
			
		||||
    // start and end point is from the change_filament_gcode
 | 
			
		||||
    def->set_default_value(new ConfigOptionPoints{Vec2d(30, -3), Vec2d(54, 245)});
 | 
			
		||||
 | 
			
		||||
    def = this->add("reduce_infill_retraction", coBool);
 | 
			
		||||
    def->label = L("Reduce infill retraction");
 | 
			
		||||
    def->tooltip = L("Don't retract when the travel is in infill area absolutely. That means the oozing can't been seen. "
 | 
			
		||||
| 
						 | 
				
			
			@ -2397,6 +2416,21 @@ void PrintConfigDef::init_fff_params()
 | 
			
		|||
    def->mode = comSimple;
 | 
			
		||||
    def->set_default_value(new ConfigOptionFloats { 0.4 });
 | 
			
		||||
 | 
			
		||||
    def = this->add("z_hop_types", coEnums);
 | 
			
		||||
    def->label = L("Z Hop Type");
 | 
			
		||||
    def->tooltip = L("");
 | 
			
		||||
    def->enum_keys_map = &ConfigOptionEnum<ZHopType>::get_enum_values();
 | 
			
		||||
    def->enum_values.push_back("Auto Lift");
 | 
			
		||||
    def->enum_values.push_back("Normal Lift");
 | 
			
		||||
    def->enum_values.push_back("Slope Lift");
 | 
			
		||||
    def->enum_values.push_back("Spiral Lift");
 | 
			
		||||
    def->enum_labels.push_back(L("Auto"));
 | 
			
		||||
    def->enum_labels.push_back(L("Normal"));
 | 
			
		||||
    def->enum_labels.push_back(L("Slope"));
 | 
			
		||||
    def->enum_labels.push_back(L("Spiral"));
 | 
			
		||||
    def->mode = comAdvanced;
 | 
			
		||||
    def->set_default_value(new ConfigOptionEnumsGeneric{ ZHopType::zhtSpiral });
 | 
			
		||||
 | 
			
		||||
    def = this->add("retract_restart_extra", coFloats);
 | 
			
		||||
    def->label = L("Extra length on restart");
 | 
			
		||||
    def->tooltip = L("When the retraction is compensated after the travel move, the extruder will push "
 | 
			
		||||
| 
						 | 
				
			
			@ -2495,11 +2529,11 @@ void PrintConfigDef::init_fff_params()
 | 
			
		|||
    def->set_default_value(new ConfigOptionFloat(2));
 | 
			
		||||
 | 
			
		||||
    def = this->add("skirt_height", coInt);
 | 
			
		||||
    //def->label = L("Skirt height");
 | 
			
		||||
    def->label = "Skirt height";
 | 
			
		||||
    //def->tooltip = L("How many layers of skirt. Usually only one layer");
 | 
			
		||||
    def->label = L("Skirt height");
 | 
			
		||||
    //def->label = "Skirt height";
 | 
			
		||||
    def->tooltip = L("How many layers of skirt. Usually only one layer");
 | 
			
		||||
    def->sidetext = L("layers");
 | 
			
		||||
    def->mode = comAdvanced;
 | 
			
		||||
    def->mode = comSimple;
 | 
			
		||||
    def->max = 10000;
 | 
			
		||||
    def->set_default_value(new ConfigOptionInt(1));
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3226,7 +3260,7 @@ void PrintConfigDef::init_fff_params()
 | 
			
		|||
    //def->sidetext = L("mm");
 | 
			
		||||
    def->mode = comDevelop;
 | 
			
		||||
    // BBS: change data type to floats to add partplate logic
 | 
			
		||||
    def->set_default_value(new ConfigOptionFloats{ 240. });
 | 
			
		||||
    def->set_default_value(new ConfigOptionFloats{ 220. });
 | 
			
		||||
 | 
			
		||||
    def = this->add("prime_tower_width", coFloat);
 | 
			
		||||
    def->label = L("Width");
 | 
			
		||||
| 
						 | 
				
			
			@ -3409,7 +3443,7 @@ void PrintConfigDef::init_fff_params()
 | 
			
		|||
    // Declare retract values for filament profile, overriding the printer's extruder profile.
 | 
			
		||||
    for (const char *opt_key : {
 | 
			
		||||
        // floats
 | 
			
		||||
        "retraction_length", "z_hop", "retraction_speed", "deretraction_speed", "retract_restart_extra", "retraction_minimum_travel",
 | 
			
		||||
        "retraction_length", "z_hop", "z_hop_types", "retraction_speed", "deretraction_speed", "retract_restart_extra", "retraction_minimum_travel",
 | 
			
		||||
        // BBS: floats
 | 
			
		||||
        "wipe_distance",
 | 
			
		||||
        // bools
 | 
			
		||||
| 
						 | 
				
			
			@ -3423,6 +3457,9 @@ void PrintConfigDef::init_fff_params()
 | 
			
		|||
        def->full_label = it_opt->second.full_label;
 | 
			
		||||
        def->tooltip 	= it_opt->second.tooltip;
 | 
			
		||||
        def->sidetext   = it_opt->second.sidetext;
 | 
			
		||||
        def->enum_keys_map = it_opt->second.enum_keys_map;
 | 
			
		||||
        def->enum_labels   = it_opt->second.enum_labels;
 | 
			
		||||
        def->enum_values   = it_opt->second.enum_values;
 | 
			
		||||
        //BBS: shown specific filament retract config because we hide the machine retract into comDevelop mode
 | 
			
		||||
        if ((strcmp(opt_key, "retraction_length") == 0) ||
 | 
			
		||||
            (strcmp(opt_key, "z_hop") == 0))
 | 
			
		||||
| 
						 | 
				
			
			@ -3433,6 +3470,7 @@ void PrintConfigDef::init_fff_params()
 | 
			
		|||
        case coFloats   : def->set_default_value(new ConfigOptionFloatsNullable  (static_cast<const ConfigOptionFloats*  >(it_opt->second.default_value.get())->values)); break;
 | 
			
		||||
        case coPercents : def->set_default_value(new ConfigOptionPercentsNullable(static_cast<const ConfigOptionPercents*>(it_opt->second.default_value.get())->values)); break;
 | 
			
		||||
        case coBools    : def->set_default_value(new ConfigOptionBoolsNullable   (static_cast<const ConfigOptionBools*   >(it_opt->second.default_value.get())->values)); break;
 | 
			
		||||
        case coEnums    : def->set_default_value(new ConfigOptionEnumsGenericNullable(static_cast<const ConfigOptionEnumsGeneric*   >(it_opt->second.default_value.get())->values)); break;
 | 
			
		||||
        default: assert(false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -3452,7 +3490,7 @@ void PrintConfigDef::init_extruder_option_keys()
 | 
			
		|||
    // ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings
 | 
			
		||||
    m_extruder_option_keys = {
 | 
			
		||||
        "nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset",
 | 
			
		||||
        "retraction_length", "z_hop", "retraction_speed", "deretraction_speed",
 | 
			
		||||
        "retraction_length", "z_hop", "z_hop_types", "retraction_speed", "deretraction_speed",
 | 
			
		||||
        "retract_before_wipe", "retract_restart_extra", "retraction_minimum_travel", "wipe", "wipe_distance",
 | 
			
		||||
        "retract_when_changing_layer", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour",
 | 
			
		||||
        "default_filament_profile"
 | 
			
		||||
| 
						 | 
				
			
			@ -3468,7 +3506,8 @@ void PrintConfigDef::init_extruder_option_keys()
 | 
			
		|||
        "retraction_speed",
 | 
			
		||||
        "wipe",
 | 
			
		||||
        "wipe_distance",
 | 
			
		||||
        "z_hop"
 | 
			
		||||
        "z_hop",
 | 
			
		||||
        "z_hop_types"
 | 
			
		||||
    };
 | 
			
		||||
    assert(std::is_sorted(m_extruder_retract_keys.begin(), m_extruder_retract_keys.end()));
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3477,7 +3516,7 @@ void PrintConfigDef::init_filament_option_keys()
 | 
			
		|||
{
 | 
			
		||||
    m_filament_option_keys = {
 | 
			
		||||
        "filament_diameter", "min_layer_height", "max_layer_height",
 | 
			
		||||
        "retraction_length", "z_hop", "retraction_speed", "deretraction_speed",
 | 
			
		||||
        "retraction_length", "z_hop", "z_hop_types", "retraction_speed", "deretraction_speed",
 | 
			
		||||
        "retract_before_wipe", "retract_restart_extra", "retraction_minimum_travel", "wipe", "wipe_distance",
 | 
			
		||||
        "retract_when_changing_layer", "retract_length_toolchange", "retract_restart_extra_toolchange", "filament_colour",
 | 
			
		||||
        "default_filament_profile"/*,"filament_seam_gap"*/
 | 
			
		||||
| 
						 | 
				
			
			@ -3493,7 +3532,8 @@ void PrintConfigDef::init_filament_option_keys()
 | 
			
		|||
        "retraction_speed",
 | 
			
		||||
        "wipe",
 | 
			
		||||
        "wipe_distance",
 | 
			
		||||
        "z_hop"
 | 
			
		||||
        "z_hop",
 | 
			
		||||
        "z_hop_types"
 | 
			
		||||
    };
 | 
			
		||||
    assert(std::is_sorted(m_filament_retract_keys.begin(), m_filament_retract_keys.end()));
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4219,7 +4259,8 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
 | 
			
		|||
        "support_closing_radius",
 | 
			
		||||
        "remove_freq_sweep", "remove_bed_leveling", "remove_extrusion_calibration",
 | 
			
		||||
        "support_transition_line_width", "support_transition_speed", "bed_temperature", "bed_temperature_initial_layer",
 | 
			
		||||
        "can_switch_nozzle_type", "can_add_auxiliary_fan", "extra_flush_volume", "spaghetti_detector", "adaptive_layer_height"
 | 
			
		||||
        "can_switch_nozzle_type", "can_add_auxiliary_fan", "extra_flush_volume", "spaghetti_detector", "adaptive_layer_height",
 | 
			
		||||
        "z_hop_type"
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (ignore.find(opt_key) != ignore.end()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -4394,7 +4435,7 @@ void DynamicPrintConfig::normalize_fdm_1()
 | 
			
		|||
    return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
t_config_option_keys DynamicPrintConfig::normalize_fdm_2(int used_filaments)
 | 
			
		||||
t_config_option_keys DynamicPrintConfig::normalize_fdm_2(int num_objects, int used_filaments)
 | 
			
		||||
{
 | 
			
		||||
    t_config_option_keys changed_keys;
 | 
			
		||||
    ConfigOptionBool* ept_opt = this->option<ConfigOptionBool>("enable_prime_tower");
 | 
			
		||||
| 
						 | 
				
			
			@ -4405,7 +4446,7 @@ t_config_option_keys DynamicPrintConfig::normalize_fdm_2(int used_filaments)
 | 
			
		|||
 | 
			
		||||
        ConfigOptionEnum<TimelapseType>* timelapse_opt = this->option<ConfigOptionEnum<TimelapseType>>("timelapse_type");
 | 
			
		||||
        bool is_smooth_timelapse = timelapse_opt != nullptr && timelapse_opt->value == TimelapseType::tlSmooth;
 | 
			
		||||
        if (!is_smooth_timelapse && (used_filaments == 1 || ps_opt->value == PrintSequence::ByObject)) {
 | 
			
		||||
        if (!is_smooth_timelapse && (used_filaments == 1 || (ps_opt->value == PrintSequence::ByObject && num_objects > 1))) {
 | 
			
		||||
            if (ept_opt->value) {
 | 
			
		||||
                ept_opt->value = false;
 | 
			
		||||
                changed_keys.push_back("enable_prime_tower");
 | 
			
		||||
| 
						 | 
				
			
			@ -4498,7 +4539,8 @@ void DynamicPrintConfig::set_num_filaments(unsigned int num_filaments)
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string DynamicPrintConfig::validate()
 | 
			
		||||
//BBS: pass map to recording all invalid valies
 | 
			
		||||
std::map<std::string, std::string> DynamicPrintConfig::validate(bool under_cli)
 | 
			
		||||
{
 | 
			
		||||
    // Full print config is initialized from the defaults.
 | 
			
		||||
    const ConfigOption *opt = this->option("printer_technology", false);
 | 
			
		||||
| 
						 | 
				
			
			@ -4509,11 +4551,11 @@ std::string DynamicPrintConfig::validate()
 | 
			
		|||
        FullPrintConfig fpc;
 | 
			
		||||
        fpc.apply(*this, true);
 | 
			
		||||
        // Verify this print options through the FullPrintConfig.
 | 
			
		||||
        return Slic3r::validate(fpc);
 | 
			
		||||
        return Slic3r::validate(fpc, under_cli);
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
        //FIXME no validation on SLA data?
 | 
			
		||||
        return std::string();
 | 
			
		||||
        return std::map<std::string, std::string>();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -4584,38 +4626,50 @@ bool DynamicPrintConfig::is_custom_defined()
 | 
			
		|||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//BBS: pass map to recording all invalid valies
 | 
			
		||||
//FIXME localize this function.
 | 
			
		||||
std::string validate(const FullPrintConfig &cfg)
 | 
			
		||||
std::map<std::string, std::string> validate(const FullPrintConfig &cfg, bool under_cli)
 | 
			
		||||
{
 | 
			
		||||
    std::map<std::string, std::string> error_message;
 | 
			
		||||
    // --layer-height
 | 
			
		||||
    if (cfg.get_abs_value("layer_height") <= 0)
 | 
			
		||||
        return "Invalid value for --layer-height";
 | 
			
		||||
    if (fabs(fmod(cfg.get_abs_value("layer_height"), SCALING_FACTOR)) > 1e-4)
 | 
			
		||||
        return "--layer-height must be a multiple of print resolution";
 | 
			
		||||
    if (cfg.get_abs_value("layer_height") <= 0) {
 | 
			
		||||
        error_message.emplace("layer_height", L("invalid value ") + std::to_string(cfg.get_abs_value("layer_height")));
 | 
			
		||||
    }
 | 
			
		||||
    else if (fabs(fmod(cfg.get_abs_value("layer_height"), SCALING_FACTOR)) > 1e-4) {
 | 
			
		||||
        error_message.emplace("layer_height", L("invalid value ") + std::to_string(cfg.get_abs_value("layer_height")));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // --first-layer-height
 | 
			
		||||
    if (cfg.initial_layer_print_height.value <= 0)
 | 
			
		||||
        return "Invalid value for --first-layer-height";
 | 
			
		||||
    if (cfg.initial_layer_print_height.value <= 0) {
 | 
			
		||||
        error_message.emplace("initial_layer_print_height", L("invalid value ") + std::to_string(cfg.initial_layer_print_height.value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // --filament-diameter
 | 
			
		||||
    for (double fd : cfg.filament_diameter.values)
 | 
			
		||||
        if (fd < 1)
 | 
			
		||||
            return "Invalid value for --filament-diameter";
 | 
			
		||||
        if (fd < 1) {
 | 
			
		||||
            error_message.emplace("filament_diameter", L("invalid value ") + cfg.filament_diameter.serialize());
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    // --nozzle-diameter
 | 
			
		||||
    for (double nd : cfg.nozzle_diameter.values)
 | 
			
		||||
        if (nd < 0.005)
 | 
			
		||||
            return "Invalid value for --nozzle-diameter";
 | 
			
		||||
        if (nd < 0.005) {
 | 
			
		||||
            error_message.emplace("nozzle_diameter", L("invalid value ") + cfg.nozzle_diameter.serialize());
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    // --perimeters
 | 
			
		||||
    if (cfg.wall_loops.value < 0)
 | 
			
		||||
        return "Invalid value for --wall_loops";
 | 
			
		||||
    if (cfg.wall_loops.value < 0) {
 | 
			
		||||
        error_message.emplace("wall_loops", L("invalid value ") + std::to_string(cfg.wall_loops.value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // --solid-layers
 | 
			
		||||
    if (cfg.top_shell_layers < 0)
 | 
			
		||||
        return "Invalid value for --top-solid-layers";
 | 
			
		||||
    if (cfg.bottom_shell_layers < 0)
 | 
			
		||||
        return "Invalid value for --bottom-solid-layers";
 | 
			
		||||
    if (cfg.top_shell_layers < 0) {
 | 
			
		||||
        error_message.emplace("top_shell_layers", L("invalid value ") + std::to_string(cfg.top_shell_layers));
 | 
			
		||||
    }
 | 
			
		||||
    if (cfg.bottom_shell_layers < 0) {
 | 
			
		||||
        error_message.emplace("bottom_shell_layers", L("invalid value ") + std::to_string(cfg.bottom_shell_layers));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (cfg.use_firmware_retraction.value &&
 | 
			
		||||
        cfg.gcode_flavor.value != gcfKlipper &&
 | 
			
		||||
| 
						 | 
				
			
			@ -4626,69 +4680,96 @@ std::string validate(const FullPrintConfig &cfg)
 | 
			
		|||
        cfg.gcode_flavor.value != gcfMarlinFirmware &&
 | 
			
		||||
        cfg.gcode_flavor.value != gcfMachinekit &&
 | 
			
		||||
        cfg.gcode_flavor.value != gcfRepetier)
 | 
			
		||||
        return "--use-firmware-retraction is only supported by Klipper, Marlin, Smoothie, RepRapFirmware, Repetier and Machinekit firmware";
 | 
			
		||||
        error_message.emplace("use_firmware_retraction","--use-firmware-retraction is only supported by Klipper, Marlin, Smoothie, RepRapFirmware, Repetier and Machinekit firmware");
 | 
			
		||||
 | 
			
		||||
    if (cfg.use_firmware_retraction.value)
 | 
			
		||||
        for (unsigned char wipe : cfg.wipe.values)
 | 
			
		||||
             if (wipe)
 | 
			
		||||
                return "--use-firmware-retraction is not compatible with --wipe";
 | 
			
		||||
                error_message.emplace("use_firmware_retraction", "--use-firmware-retraction is not compatible with --wipe");
 | 
			
		||||
                
 | 
			
		||||
    // --gcode-flavor
 | 
			
		||||
    if (! print_config_def.get("gcode_flavor")->has_enum_value(cfg.gcode_flavor.serialize()))
 | 
			
		||||
        return "Invalid value for --gcode-flavor";
 | 
			
		||||
    if (! print_config_def.get("gcode_flavor")->has_enum_value(cfg.gcode_flavor.serialize())) {
 | 
			
		||||
        error_message.emplace("gcode_flavor", L("invalid value ") + cfg.gcode_flavor.serialize());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // --fill-pattern
 | 
			
		||||
    if (! print_config_def.get("sparse_infill_pattern")->has_enum_value(cfg.sparse_infill_pattern.serialize()))
 | 
			
		||||
        return "Invalid value for --fill-pattern";
 | 
			
		||||
    if (! print_config_def.get("sparse_infill_pattern")->has_enum_value(cfg.sparse_infill_pattern.serialize())) {
 | 
			
		||||
        error_message.emplace("sparse_infill_pattern", L("invalid value ") + cfg.sparse_infill_pattern.serialize());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // --top-fill-pattern
 | 
			
		||||
    if (! print_config_def.get("top_surface_pattern")->has_enum_value(cfg.top_surface_pattern.serialize()))
 | 
			
		||||
        return "Invalid value for --top-fill-pattern";
 | 
			
		||||
    if (! print_config_def.get("top_surface_pattern")->has_enum_value(cfg.top_surface_pattern.serialize())) {
 | 
			
		||||
        error_message.emplace("top_surface_pattern", L("invalid value ") + cfg.top_surface_pattern.serialize());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // --bottom-fill-pattern
 | 
			
		||||
    if (! print_config_def.get("bottom_surface_pattern")->has_enum_value(cfg.bottom_surface_pattern.serialize()))
 | 
			
		||||
        return "Invalid value for --bottom-fill-pattern";
 | 
			
		||||
    if (! print_config_def.get("bottom_surface_pattern")->has_enum_value(cfg.bottom_surface_pattern.serialize())) {
 | 
			
		||||
        error_message.emplace("bottom_surface_pattern", L("invalid value ") + cfg.bottom_surface_pattern.serialize());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // --fill-density
 | 
			
		||||
    if (fabs(cfg.sparse_infill_density.value - 100.) < EPSILON &&
 | 
			
		||||
        ! print_config_def.get("top_surface_pattern")->has_enum_value(cfg.sparse_infill_pattern.serialize()))
 | 
			
		||||
        return "The selected fill pattern is not supposed to work at 100% density";
 | 
			
		||||
        ! print_config_def.get("top_surface_pattern")->has_enum_value(cfg.sparse_infill_pattern.serialize())) {
 | 
			
		||||
        error_message.emplace("sparse_infill_pattern", cfg.sparse_infill_pattern.serialize() + L(" doesn't work at 100%% density "));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // --skirt-height
 | 
			
		||||
    if (cfg.skirt_height < 0)
 | 
			
		||||
        return "Invalid value for --skirt-height";
 | 
			
		||||
    if (cfg.skirt_height < 0) {
 | 
			
		||||
        error_message.emplace("skirt_height", L("invalid value ") + std::to_string(cfg.skirt_height));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // --bridge-flow-ratio
 | 
			
		||||
    if (cfg.bridge_flow <= 0)
 | 
			
		||||
        return "Invalid value for --bridge-flow-ratio";
 | 
			
		||||
    if (cfg.bridge_flow <= 0) {
 | 
			
		||||
        error_message.emplace("bridge_flow", L("invalid value ") + std::to_string(cfg.bridge_flow));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // extruder clearance
 | 
			
		||||
    if (cfg.extruder_clearance_radius <= 0)
 | 
			
		||||
        return "Invalid value for --extruder-clearance-radius";
 | 
			
		||||
    if (cfg.extruder_clearance_height_to_rod <= 0)
 | 
			
		||||
        return "Invalid value for --extruder-clearance-height-to-rod";
 | 
			
		||||
    if (cfg.extruder_clearance_height_to_lid <= 0)
 | 
			
		||||
        return "Invalid value for --extruder-clearance-height-to-lid";
 | 
			
		||||
    if (cfg.extruder_clearance_radius <= 0) {
 | 
			
		||||
        error_message.emplace("extruder_clearance_radius", L("invalid value ") + std::to_string(cfg.extruder_clearance_radius));
 | 
			
		||||
    }
 | 
			
		||||
    if (cfg.extruder_clearance_height_to_rod <= 0) {
 | 
			
		||||
        error_message.emplace("extruder_clearance_height_to_rod", L("invalid value ") + std::to_string(cfg.extruder_clearance_height_to_rod));
 | 
			
		||||
    }
 | 
			
		||||
    if (cfg.extruder_clearance_height_to_lid <= 0) {
 | 
			
		||||
        error_message.emplace("extruder_clearance_height_to_lid", L("invalid value ") + std::to_string(cfg.extruder_clearance_height_to_lid));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // --extrusion-multiplier
 | 
			
		||||
    for (double em : cfg.filament_flow_ratio.values)
 | 
			
		||||
        if (em <= 0)
 | 
			
		||||
            return "Invalid value for --filament-flow-ratio";
 | 
			
		||||
        if (em <= 0) {
 | 
			
		||||
            error_message.emplace("filament_flow_ratio", L("invalid value ") + cfg.filament_flow_ratio.serialize());
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    // --spiral-vase
 | 
			
		||||
    if (cfg.spiral_mode) {
 | 
			
		||||
    //for non-cli case, we will popup dialog for spiral mode correction
 | 
			
		||||
    if (cfg.spiral_mode && under_cli) {
 | 
			
		||||
        // Note that we might want to have more than one perimeter on the bottom
 | 
			
		||||
        // solid layers.
 | 
			
		||||
        if (cfg.wall_loops > 1)
 | 
			
		||||
            return "Can't make more than one perimeter when spiral vase mode is enabled";
 | 
			
		||||
        else if (cfg.wall_loops < 1)
 | 
			
		||||
            return "Can't make less than one perimeter when spiral vase mode is enabled";
 | 
			
		||||
        if (cfg.sparse_infill_density > 0)
 | 
			
		||||
            return "Spiral vase mode can only print hollow objects, so you need to set Fill density to 0";
 | 
			
		||||
        if (cfg.top_shell_layers > 0)
 | 
			
		||||
            return "Spiral vase mode is not compatible with top solid layers";
 | 
			
		||||
        if (cfg.enable_support || cfg.enforce_support_layers > 0)
 | 
			
		||||
            return "Spiral vase mode is not compatible with support";
 | 
			
		||||
        if (cfg.wall_loops != 1) {
 | 
			
		||||
            error_message.emplace("wall_loops", L("Invalid value when spiral vase mode is enabled: ") + std::to_string(cfg.wall_loops));
 | 
			
		||||
            //return "Can't make more than one perimeter when spiral vase mode is enabled";
 | 
			
		||||
            //return "Can't make less than one perimeter when spiral vase mode is enabled";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (cfg.sparse_infill_density > 0) {
 | 
			
		||||
            error_message.emplace("sparse_infill_density", L("Invalid value when spiral vase mode is enabled: ") + std::to_string(cfg.sparse_infill_density));
 | 
			
		||||
            //return "Spiral vase mode can only print hollow objects, so you need to set Fill density to 0";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (cfg.top_shell_layers > 0) {
 | 
			
		||||
            error_message.emplace("top_shell_layers", L("Invalid value when spiral vase mode is enabled: ") + std::to_string(cfg.top_shell_layers));
 | 
			
		||||
            //return "Spiral vase mode is not compatible with top solid layers";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (cfg.enable_support ) {
 | 
			
		||||
            error_message.emplace("enable_support", L("Invalid value when spiral vase mode is enabled: ") + std::to_string(cfg.enable_support));
 | 
			
		||||
            //return "Spiral vase mode is not compatible with support";
 | 
			
		||||
        }
 | 
			
		||||
        if (cfg.enforce_support_layers > 0) {
 | 
			
		||||
            error_message.emplace("enforce_support_layers", L("Invalid value when spiral vase mode is enabled: ") + std::to_string(cfg.enforce_support_layers));
 | 
			
		||||
            //return "Spiral vase mode is not compatible with support";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // extrusion widths
 | 
			
		||||
| 
						 | 
				
			
			@ -4706,8 +4787,10 @@ std::string validate(const FullPrintConfig &cfg)
 | 
			
		|||
            "initial_layer_line_width" };
 | 
			
		||||
        for (size_t i = 0; i < sizeof(widths) / sizeof(widths[i]); ++ i) {
 | 
			
		||||
            std::string key(widths[i]);
 | 
			
		||||
            if (cfg.get_abs_value(key) > 2.5 * max_nozzle_diameter)
 | 
			
		||||
                return std::string("Too Large line width: ") + key;
 | 
			
		||||
            if (cfg.get_abs_value(key) > 2.5 * max_nozzle_diameter) {
 | 
			
		||||
                error_message.emplace(key, L("too large line width ") + std::to_string(cfg.get_abs_value(key)));
 | 
			
		||||
                //return std::string("Too Large line width: ") + key;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -4750,12 +4833,15 @@ std::string validate(const FullPrintConfig &cfg)
 | 
			
		|||
            break;
 | 
			
		||||
        default:;
 | 
			
		||||
        }
 | 
			
		||||
        if (out_of_range)
 | 
			
		||||
            return std::string("Value out of range: " + opt_key);
 | 
			
		||||
        if (out_of_range) {
 | 
			
		||||
            if (error_message.find(opt_key) == error_message.end())
 | 
			
		||||
                error_message.emplace(opt_key, opt->serialize() + L(" not in range ") +"[" + std::to_string(optdef->min) + "," + std::to_string(optdef->max) + "]");
 | 
			
		||||
            //return std::string("Value out of range: " + opt_key);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The configuration is valid.
 | 
			
		||||
    return "";
 | 
			
		||||
    return error_message;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Declare and initialize static caches of StaticPrintConfig derived classes.
 | 
			
		||||
| 
						 | 
				
			
			@ -5127,6 +5213,26 @@ Points get_bed_shape(const PrintConfig &cfg)
 | 
			
		|||
 | 
			
		||||
Points get_bed_shape(const SLAPrinterConfig &cfg) { return to_points(cfg.printable_area.values); }
 | 
			
		||||
 | 
			
		||||
Polygon get_bed_shape_with_excluded_area(const PrintConfig& cfg)
 | 
			
		||||
{
 | 
			
		||||
    Polygon bed_poly;
 | 
			
		||||
    bed_poly.points = get_bed_shape(cfg);
 | 
			
		||||
 | 
			
		||||
    Points excluse_area_points = to_points(cfg.bed_exclude_area.values);
 | 
			
		||||
    Polygons exclude_polys;
 | 
			
		||||
    Polygon exclude_poly;
 | 
			
		||||
    for (int i = 0; i < excluse_area_points.size(); i++) {
 | 
			
		||||
        auto pt = excluse_area_points[i];
 | 
			
		||||
        exclude_poly.points.emplace_back(pt);
 | 
			
		||||
        if (i % 4 == 3) {  // exclude areas are always rectangle
 | 
			
		||||
            exclude_polys.push_back(exclude_poly);
 | 
			
		||||
            exclude_poly.points.clear();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    auto tmp = diff({ bed_poly }, exclude_polys);
 | 
			
		||||
    if (!tmp.empty()) bed_poly = tmp[0];
 | 
			
		||||
    return bed_poly;
 | 
			
		||||
}
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
 | 
			
		||||
#include <cereal/types/polymorphic.hpp>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,7 @@
 | 
			
		|||
 | 
			
		||||
#include "libslic3r.h"
 | 
			
		||||
#include "Config.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Polygon.hpp"
 | 
			
		||||
#include <boost/preprocessor/facilities/empty.hpp>
 | 
			
		||||
#include <boost/preprocessor/punctuation/comma_if.hpp>
 | 
			
		||||
#include <boost/preprocessor/seq/for_each.hpp>
 | 
			
		||||
| 
						 | 
				
			
			@ -78,6 +78,7 @@ enum class WallInfillOrder {
 | 
			
		|||
enum class PrintSequence {
 | 
			
		||||
    ByLayer,
 | 
			
		||||
    ByObject,
 | 
			
		||||
    ByDefault,
 | 
			
		||||
    Count,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -207,6 +208,15 @@ enum NozzleType {
 | 
			
		|||
    ntCount
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// BBS
 | 
			
		||||
enum ZHopType {
 | 
			
		||||
    zhtAuto = 0,
 | 
			
		||||
    zhtNormal,
 | 
			
		||||
    zhtSlope,
 | 
			
		||||
    zhtSpiral,
 | 
			
		||||
    zhtCount
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static std::string bed_type_to_gcode_string(const BedType type)
 | 
			
		||||
{
 | 
			
		||||
    std::string type_str;
 | 
			
		||||
| 
						 | 
				
			
			@ -368,15 +378,16 @@ public:
 | 
			
		|||
    void                normalize_fdm(int used_filaments = 0);
 | 
			
		||||
    void                normalize_fdm_1();
 | 
			
		||||
    //return the changed param set
 | 
			
		||||
    t_config_option_keys normalize_fdm_2(int used_filaments = 0);
 | 
			
		||||
    t_config_option_keys normalize_fdm_2(int num_objects, int used_filaments = 0);
 | 
			
		||||
 | 
			
		||||
    void                set_num_extruders(unsigned int num_extruders);
 | 
			
		||||
 | 
			
		||||
    // BBS
 | 
			
		||||
    void                set_num_filaments(unsigned int num_filaments);
 | 
			
		||||
 | 
			
		||||
    //BBS
 | 
			
		||||
    // Validate the PrintConfig. Returns an empty string on success, otherwise an error message is returned.
 | 
			
		||||
    std::string         validate();
 | 
			
		||||
    std::map<std::string, std::string>         validate(bool under_cli = false);
 | 
			
		||||
 | 
			
		||||
    // Verify whether the opt_key has not been obsoleted or renamed.
 | 
			
		||||
    // Both opt_key and value may be modified by handle_legacy().
 | 
			
		||||
| 
						 | 
				
			
			@ -842,7 +853,8 @@ PRINT_CONFIG_CLASS_DEFINE(
 | 
			
		|||
    ((ConfigOptionFloats,              retraction_length))
 | 
			
		||||
    ((ConfigOptionFloats,              retract_length_toolchange))
 | 
			
		||||
    ((ConfigOptionFloats,              z_hop))
 | 
			
		||||
    ((ConfigOptionEnum<LiftType>,      z_lift_type))
 | 
			
		||||
    // BBS
 | 
			
		||||
    ((ConfigOptionEnumsGeneric,        z_hop_types))
 | 
			
		||||
    ((ConfigOptionFloats,              retract_restart_extra))
 | 
			
		||||
    ((ConfigOptionFloats,              retract_restart_extra_toolchange))
 | 
			
		||||
    ((ConfigOptionFloats,              retraction_speed))
 | 
			
		||||
| 
						 | 
				
			
			@ -978,6 +990,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
 | 
			
		|||
    // BBS: not in any preset, calculated before slicing
 | 
			
		||||
    ((ConfigOptionBool,               has_prime_tower))
 | 
			
		||||
    ((ConfigOptionFloat,              nozzle_volume))
 | 
			
		||||
    ((ConfigOptionPoints,             start_end_points))
 | 
			
		||||
    ((ConfigOptionEnum<TimelapseType>,    timelapse_type))
 | 
			
		||||
    ((ConfigOptionPoints,              thumbnails))
 | 
			
		||||
    // BBS: move from PrintObjectConfig
 | 
			
		||||
| 
						 | 
				
			
			@ -994,7 +1007,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE0(
 | 
			
		|||
)
 | 
			
		||||
 | 
			
		||||
// Validate the FullPrintConfig. Returns an empty string on success, otherwise an error message is returned.
 | 
			
		||||
std::string validate(const FullPrintConfig &config);
 | 
			
		||||
std::map<std::string, std::string> validate(const FullPrintConfig &config, bool under_cli = false);
 | 
			
		||||
 | 
			
		||||
PRINT_CONFIG_CLASS_DEFINE(
 | 
			
		||||
    SLAPrintConfig,
 | 
			
		||||
| 
						 | 
				
			
			@ -1282,6 +1295,7 @@ private:
 | 
			
		|||
Points get_bed_shape(const DynamicPrintConfig &cfg);
 | 
			
		||||
Points get_bed_shape(const PrintConfig &cfg);
 | 
			
		||||
Points get_bed_shape(const SLAPrinterConfig &cfg);
 | 
			
		||||
Slic3r::Polygon get_bed_shape_with_excluded_area(const PrintConfig& cfg);
 | 
			
		||||
 | 
			
		||||
// ModelConfig is a wrapper around DynamicPrintConfig with an addition of a timestamp.
 | 
			
		||||
// Each change of ModelConfig is tracked by assigning a new timestamp from a global counter.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -91,7 +91,6 @@ PrintObject::~PrintObject()
 | 
			
		|||
    if (m_shared_regions && -- m_shared_regions->m_ref_cnt == 0) delete m_shared_regions;
 | 
			
		||||
    clear_layers();
 | 
			
		||||
    clear_support_layers();
 | 
			
		||||
    clear_tree_support_layers();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PrintBase::ApplyStatus PrintObject::set_instances(PrintInstances &&instances)
 | 
			
		||||
| 
						 | 
				
			
			@ -410,11 +409,49 @@ void PrintObject::ironing()
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BBS
 | 
			
		||||
void PrintObject::clear_overhangs_for_lift()
 | 
			
		||||
{
 | 
			
		||||
    if (!m_shared_object) {
 | 
			
		||||
        for (Layer* l : m_layers)
 | 
			
		||||
            l->loverhangs.clear();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const float g_min_overhang_percent_for_lift = 0.3f;
 | 
			
		||||
 | 
			
		||||
void PrintObject::detect_overhangs_for_lift()
 | 
			
		||||
{
 | 
			
		||||
    if (this->set_started(posDetectOverhangsForLift)) {
 | 
			
		||||
        const float min_overlap = m_config.line_width * g_min_overhang_percent_for_lift;
 | 
			
		||||
        size_t num_layers = this->layer_count();
 | 
			
		||||
        size_t num_raft_layers = m_slicing_params.raft_layers();
 | 
			
		||||
 | 
			
		||||
        m_print->set_status(78, L("Detect overhangs for auto-lift"));
 | 
			
		||||
 | 
			
		||||
        this->clear_overhangs_for_lift();
 | 
			
		||||
 | 
			
		||||
        tbb::spin_mutex layer_storage_mutex;
 | 
			
		||||
        tbb::parallel_for(tbb::blocked_range<size_t>(num_raft_layers + 1, num_layers),
 | 
			
		||||
            [this, min_overlap](const tbb::blocked_range<size_t>& range)
 | 
			
		||||
            {
 | 
			
		||||
                for (size_t layer_id = range.begin(); layer_id < range.end(); ++layer_id) {
 | 
			
		||||
                    Layer& layer = *m_layers[layer_id];
 | 
			
		||||
                    Layer& lower_layer = *layer.lower_layer;
 | 
			
		||||
 | 
			
		||||
                    ExPolygons overhangs = diff_ex(layer.lslices, offset_ex(lower_layer.lslices, scale_(min_overlap)));
 | 
			
		||||
                    layer.loverhangs = std::move(offset2_ex(overhangs, -0.1f * scale_(m_config.line_width), 0.1f * scale_(m_config.line_width)));
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        this->set_done(posDetectOverhangsForLift);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PrintObject::generate_support_material()
 | 
			
		||||
{
 | 
			
		||||
    if (this->set_started(posSupportMaterial)) {
 | 
			
		||||
        this->clear_support_layers();
 | 
			
		||||
        this->clear_tree_support_layers();
 | 
			
		||||
 | 
			
		||||
        if ((this->has_support() && m_layers.size() > 1) || (this->has_raft() && ! m_layers.empty())) {
 | 
			
		||||
            m_print->set_status(50, L("Generating support"));
 | 
			
		||||
| 
						 | 
				
			
			@ -483,16 +520,6 @@ void PrintObject::simplify_extrusion_path()
 | 
			
		|||
            }
 | 
			
		||||
        );
 | 
			
		||||
        m_print->throw_if_canceled();
 | 
			
		||||
        tbb::parallel_for(
 | 
			
		||||
            tbb::blocked_range<size_t>(0, m_tree_support_layers.size()),
 | 
			
		||||
            [this](const tbb::blocked_range<size_t>& range) {
 | 
			
		||||
                for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
 | 
			
		||||
                    m_print->throw_if_canceled();
 | 
			
		||||
                    m_tree_support_layers[layer_idx]->simplify_support_extrusion_path();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        m_print->throw_if_canceled();
 | 
			
		||||
        BOOST_LOG_TRIVIAL(debug) << "Simplify extrusion path of support in parallel - end";
 | 
			
		||||
        this->set_done(posSimplifySupportPath);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -564,19 +591,6 @@ Layer* PrintObject::add_layer(int id, coordf_t height, coordf_t print_z, coordf_
 | 
			
		|||
    return m_layers.back();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BBS
 | 
			
		||||
const TreeSupportLayer* PrintObject::get_tree_support_layer_at_printz(coordf_t print_z, coordf_t epsilon) const
 | 
			
		||||
{
 | 
			
		||||
    coordf_t limit = print_z - epsilon;
 | 
			
		||||
    auto it = Slic3r::lower_bound_by_predicate(m_tree_support_layers.begin(), m_tree_support_layers.end(), [limit](const TreeSupportLayer* layer) { return layer->print_z < limit; });
 | 
			
		||||
    return (it == m_tree_support_layers.end() || (*it)->print_z > print_z + epsilon) ? nullptr : *it;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TreeSupportLayer* PrintObject::get_tree_support_layer_at_printz(coordf_t print_z, coordf_t epsilon)
 | 
			
		||||
{
 | 
			
		||||
    return const_cast<TreeSupportLayer*>(std::as_const(*this).get_tree_support_layer_at_printz(print_z, epsilon));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const SupportLayer* PrintObject::get_support_layer_at_printz(coordf_t print_z, coordf_t epsilon) const
 | 
			
		||||
{
 | 
			
		||||
    coordf_t limit = print_z - epsilon;
 | 
			
		||||
| 
						 | 
				
			
			@ -589,12 +603,17 @@ SupportLayer* PrintObject::get_support_layer_at_printz(coordf_t print_z, coordf_
 | 
			
		|||
    return const_cast<SupportLayer*>(std::as_const(*this).get_support_layer_at_printz(print_z, epsilon));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PrintObject::clear_tree_support_layers()
 | 
			
		||||
void PrintObject::clear_support_layers()
 | 
			
		||||
{
 | 
			
		||||
    if (!m_shared_object) {
 | 
			
		||||
        for (TreeSupportLayer* l : m_tree_support_layers)
 | 
			
		||||
        for (SupportLayer* l : m_support_layers)
 | 
			
		||||
            delete l;
 | 
			
		||||
        m_tree_support_layers.clear();
 | 
			
		||||
        m_support_layers.clear();
 | 
			
		||||
        for (auto l : m_layers) {
 | 
			
		||||
            l->sharp_tails.clear();
 | 
			
		||||
            l->sharp_tails_height.clear();
 | 
			
		||||
            l->cantilevers.clear();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -614,19 +633,11 @@ std::shared_ptr<TreeSupportData> PrintObject::alloc_tree_support_preview_cache()
 | 
			
		|||
    return m_tree_support_preview_cache;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TreeSupportLayer* PrintObject::add_tree_support_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z)
 | 
			
		||||
SupportLayer* PrintObject::add_tree_support_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z)
 | 
			
		||||
{
 | 
			
		||||
    m_tree_support_layers.emplace_back(new TreeSupportLayer(id, this, height, print_z, slice_z));
 | 
			
		||||
    return m_tree_support_layers.back();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PrintObject::clear_support_layers()
 | 
			
		||||
{
 | 
			
		||||
    if (!m_shared_object) {
 | 
			
		||||
        for (Layer *l : m_support_layers)
 | 
			
		||||
            delete l;
 | 
			
		||||
        m_support_layers.clear();
 | 
			
		||||
    }
 | 
			
		||||
    m_support_layers.emplace_back(new SupportLayer(id, 0, this, height, print_z, slice_z));
 | 
			
		||||
    m_support_layers.back()->support_type = stInnerTree;
 | 
			
		||||
    return m_support_layers.back();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SupportLayer* PrintObject::add_support_layer(int id, int interface_id, coordf_t height, coordf_t print_z)
 | 
			
		||||
| 
						 | 
				
			
			@ -717,7 +728,14 @@ bool PrintObject::invalidate_state_by_config_options(
 | 
			
		|||
            || opt_key == "support_top_z_distance"
 | 
			
		||||
            || opt_key == "support_bottom_z_distance"
 | 
			
		||||
            || opt_key == "xy_hole_compensation"
 | 
			
		||||
            || opt_key == "xy_contour_compensation") {
 | 
			
		||||
            || opt_key == "xy_contour_compensation"
 | 
			
		||||
            //BBS: [Arthur] the following params affect bottomBridge surface type detection
 | 
			
		||||
            || opt_key == "support_type"
 | 
			
		||||
            || opt_key == "bridge_no_support"
 | 
			
		||||
            || opt_key == "max_bridge_length"
 | 
			
		||||
            || opt_key == "support_interface_top_layers"
 | 
			
		||||
            || opt_key == "support_critical_regions_only"
 | 
			
		||||
            ) {
 | 
			
		||||
            steps.emplace_back(posSlice);
 | 
			
		||||
        } else if (opt_key == "enable_support") {
 | 
			
		||||
            steps.emplace_back(posSupportMaterial);
 | 
			
		||||
| 
						 | 
				
			
			@ -762,9 +780,23 @@ bool PrintObject::invalidate_state_by_config_options(
 | 
			
		|||
            || opt_key == "tree_support_branch_angle"
 | 
			
		||||
            || opt_key == "tree_support_wall_count") {
 | 
			
		||||
            steps.emplace_back(posSupportMaterial);
 | 
			
		||||
        } else if (opt_key == "bottom_shell_layers") {
 | 
			
		||||
        } else if (
 | 
			
		||||
               opt_key == "bottom_shell_layers"
 | 
			
		||||
            || opt_key == "top_shell_layers") {
 | 
			
		||||
            
 | 
			
		||||
            steps.emplace_back(posPrepareInfill);
 | 
			
		||||
            if (m_print->config().spiral_mode) {
 | 
			
		||||
 | 
			
		||||
            const auto *old_shell_layers = old_config.option<ConfigOptionInt>(opt_key);
 | 
			
		||||
            const auto *new_shell_layers = new_config.option<ConfigOptionInt>(opt_key);
 | 
			
		||||
            assert(old_shell_layers && new_shell_layers);
 | 
			
		||||
 | 
			
		||||
            bool value_changed = (old_shell_layers->value == 0 && new_shell_layers->value > 0) ||
 | 
			
		||||
                                 (old_shell_layers->value > 0 && new_shell_layers->value == 0);
 | 
			
		||||
 | 
			
		||||
            if (value_changed && this->object_extruders().size() > 1) {
 | 
			
		||||
                steps.emplace_back(posSlice);
 | 
			
		||||
            }               
 | 
			
		||||
            else if (m_print->config().spiral_mode && opt_key == "bottom_shell_layers") {
 | 
			
		||||
                // Changing the number of bottom layers when a spiral vase is enabled requires re-slicing the object again.
 | 
			
		||||
                // Otherwise, holes in the bottom layers could be filled, as is reported in GH #5528.
 | 
			
		||||
                steps.emplace_back(posSlice);
 | 
			
		||||
| 
						 | 
				
			
			@ -773,7 +805,6 @@ bool PrintObject::invalidate_state_by_config_options(
 | 
			
		|||
               opt_key == "interface_shells"
 | 
			
		||||
            || opt_key == "infill_combination"
 | 
			
		||||
            || opt_key == "bottom_shell_thickness"
 | 
			
		||||
            || opt_key == "top_shell_layers"
 | 
			
		||||
            || opt_key == "top_shell_thickness"
 | 
			
		||||
            || opt_key == "minimum_sparse_infill_area"
 | 
			
		||||
            || opt_key == "sparse_infill_filament"
 | 
			
		||||
| 
						 | 
				
			
			@ -971,9 +1002,17 @@ void PrintObject::detect_surfaces_type()
 | 
			
		|||
            [this, region_id, interface_shells, &surfaces_new](const tbb::blocked_range<size_t>& range) {
 | 
			
		||||
                // If we have soluble support material, don't bridge. The overhang will be squished against a soluble layer separating
 | 
			
		||||
                // the support from the print.
 | 
			
		||||
                SurfaceType surface_type_bottom_other =
 | 
			
		||||
                    (this->has_support() && m_config.support_top_z_distance.value == 0) ?
 | 
			
		||||
                    stBottom : stBottomBridge;
 | 
			
		||||
                // BBS: the above logic only applys for normal(auto) support. Complete logic:
 | 
			
		||||
                // 1. has support, top z distance=0 (soluble material), auto support
 | 
			
		||||
                // 2. for normal(auto), bridge_no_support is off
 | 
			
		||||
                // 3. for tree(auto), interface top layers=0, max bridge length=0, support_critical_regions_only=false (only in this way the bridge is fully supported)
 | 
			
		||||
                bool bottom_is_fully_supported = this->has_support() && m_config.support_top_z_distance.value == 0 && is_auto(m_config.support_type.value);
 | 
			
		||||
                if (m_config.support_type.value == stNormalAuto)
 | 
			
		||||
                    bottom_is_fully_supported &= !m_config.bridge_no_support.value;
 | 
			
		||||
                else if (m_config.support_type.value == stTreeAuto) {
 | 
			
		||||
                    bottom_is_fully_supported &= (m_config.support_interface_top_layers.value > 0 && m_config.max_bridge_length.value == 0 && m_config.support_critical_regions_only.value==false);
 | 
			
		||||
                }
 | 
			
		||||
                SurfaceType surface_type_bottom_other = bottom_is_fully_supported ? stBottom : stBottomBridge;
 | 
			
		||||
                for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
 | 
			
		||||
                    m_print->throw_if_canceled();
 | 
			
		||||
                    // BOOST_LOG_TRIVIAL(trace) << "Detecting solid surfaces for region " << region_id << " and layer " << layer->print_z;
 | 
			
		||||
| 
						 | 
				
			
			@ -2389,7 +2428,7 @@ void PrintObject::_generate_support_material()
 | 
			
		|||
    support_material.generate(*this);
 | 
			
		||||
 | 
			
		||||
    TreeSupport tree_support(*this, m_slicing_params);
 | 
			
		||||
    tree_support.generate_support_areas();
 | 
			
		||||
    tree_support.generate();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BBS
 | 
			
		||||
| 
						 | 
				
			
			@ -2545,6 +2584,7 @@ template void PrintObject::remove_bridges_from_contacts<Polygons>(
 | 
			
		|||
 | 
			
		||||
SupportNecessaryType PrintObject::is_support_necessary()
 | 
			
		||||
{
 | 
			
		||||
#if 0
 | 
			
		||||
    static const double super_overhang_area_threshold = SQ(scale_(5.0));
 | 
			
		||||
 | 
			
		||||
    double threshold_rad = (m_config.support_threshold_angle.value < EPSILON ? 30 : m_config.support_threshold_angle.value + 1) * M_PI / 180.;
 | 
			
		||||
| 
						 | 
				
			
			@ -2627,7 +2667,16 @@ SupportNecessaryType PrintObject::is_support_necessary()
 | 
			
		|||
        if (!exceed_overhang.empty())
 | 
			
		||||
            return LargeOverhang;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#else
 | 
			
		||||
    TreeSupport tree_support(*this, m_slicing_params);
 | 
			
		||||
    tree_support.support_type = SupportType::stTreeAuto; // need to set support type to fully utilize the power of feature detection
 | 
			
		||||
    tree_support.detect_overhangs();
 | 
			
		||||
    this->clear_support_layers();
 | 
			
		||||
    if (tree_support.has_sharp_tails)
 | 
			
		||||
        return SharpTail;
 | 
			
		||||
    else if (tree_support.has_cantilever)
 | 
			
		||||
        return LargeOverhang;
 | 
			
		||||
#endif
 | 
			
		||||
    return NoNeedSupp;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2820,10 +2869,6 @@ static void project_triangles_to_slabs(ConstLayerPtrsAdaptor layers, const index
 | 
			
		|||
void PrintObject::project_and_append_custom_facets(
 | 
			
		||||
        bool seam, EnforcerBlockerType type, std::vector<Polygons>& out) const
 | 
			
		||||
{
 | 
			
		||||
    // BBS: Approve adding enforcer support on vertical faces
 | 
			
		||||
    SlabSlicingConfig config;
 | 
			
		||||
    config.isVertical = type == EnforcerBlockerType::ENFORCER ? true : false;
 | 
			
		||||
 | 
			
		||||
    for (const ModelVolume* mv : this->model_object()->volumes)
 | 
			
		||||
        if (mv->is_model_part()) {
 | 
			
		||||
            const indexed_triangle_set custom_facets = seam
 | 
			
		||||
| 
						 | 
				
			
			@ -2837,7 +2882,7 @@ void PrintObject::project_and_append_custom_facets(
 | 
			
		|||
                else {
 | 
			
		||||
                    std::vector<Polygons> projected;
 | 
			
		||||
                    // Support blockers or enforcers. Project downward facing painted areas upwards to their respective slicing plane.
 | 
			
		||||
                    slice_mesh_slabs(custom_facets, zs_from_layers(this->layers()), this->trafo_centered() * mv->get_matrix(), nullptr, &projected, [](){}, config);
 | 
			
		||||
                    slice_mesh_slabs(custom_facets, zs_from_layers(this->layers()), this->trafo_centered() * mv->get_matrix(), nullptr, &projected, [](){});
 | 
			
		||||
                    // Merge these projections with the output, layer by layer.
 | 
			
		||||
                    assert(! projected.empty());
 | 
			
		||||
                    assert(out.empty() || out.size() == projected.size());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -458,6 +458,151 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
 | 
			
		|||
    return slices_by_region;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//BBS: justify whether a volume is connected to another one
 | 
			
		||||
bool doesVolumeIntersect(VolumeSlices& vs1, VolumeSlices& vs2)
 | 
			
		||||
{
 | 
			
		||||
    if (vs1.volume_id == vs2.volume_id) return true;
 | 
			
		||||
    if (vs1.slices.size() != vs2.slices.size()) return false;
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i != vs1.slices.size(); ++i) {
 | 
			
		||||
 | 
			
		||||
        if (vs1.slices[i].empty()) continue;
 | 
			
		||||
        if (!vs2.slices[i].empty() && !intersection_ex(vs1.slices[i], vs2.slices[i]).empty()) return true;
 | 
			
		||||
        if (i + 1 != vs2.slices.size() && !vs2.slices[i + 1].empty()) {
 | 
			
		||||
            if (!intersection_ex(vs1.slices[i], vs2.slices[i + 1]).empty()) return true;
 | 
			
		||||
        }
 | 
			
		||||
        if (i - 1 >= 0 && !vs2.slices[i - 1].empty()) {
 | 
			
		||||
            if (!intersection_ex(vs1.slices[i], vs2.slices[i - 1]).empty()) return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//BBS: grouping the volumes of an object according to their connection relationship
 | 
			
		||||
bool groupingVolumes(std::vector<VolumeSlices> objSliceByVolume, std::vector<groupedVolumeSlices>& groups, double resolution, int firstLayerReplacedBy)
 | 
			
		||||
{
 | 
			
		||||
    std::vector<int> groupIndex(objSliceByVolume.size(), -1);
 | 
			
		||||
    double offsetValue = 0.05 / SCALING_FACTOR;
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i != objSliceByVolume.size(); ++i) {
 | 
			
		||||
        for (int j = 0; j != objSliceByVolume[i].slices.size(); ++j) {
 | 
			
		||||
            objSliceByVolume[i].slices[j] = offset_ex(objSliceByVolume[i].slices[j], offsetValue);
 | 
			
		||||
            for (ExPolygon& poly_ex : objSliceByVolume[i].slices[j])
 | 
			
		||||
                poly_ex.douglas_peucker(resolution);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i != objSliceByVolume.size(); ++i) {
 | 
			
		||||
        if (groupIndex[i] < 0) {
 | 
			
		||||
            groupIndex[i] = i;
 | 
			
		||||
        }
 | 
			
		||||
        for (int j = i + 1; j != objSliceByVolume.size(); ++j) {
 | 
			
		||||
            if (doesVolumeIntersect(objSliceByVolume[i], objSliceByVolume[j])) {
 | 
			
		||||
                if (groupIndex[j] < 0) groupIndex[j] = groupIndex[i];
 | 
			
		||||
                if (groupIndex[j] != groupIndex[i]) {
 | 
			
		||||
                    int retain = std::min(groupIndex[i], groupIndex[j]);
 | 
			
		||||
                    int cover = std::max(groupIndex[i], groupIndex[j]);
 | 
			
		||||
                    for (int k = 0; k != objSliceByVolume.size(); ++k) {
 | 
			
		||||
                        if (groupIndex[k] == cover) groupIndex[k] = retain;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::vector<int> groupVector{};
 | 
			
		||||
    for (int gi : groupIndex) {
 | 
			
		||||
        bool exist = false;
 | 
			
		||||
        for (int gv : groupVector) {
 | 
			
		||||
            if (gv == gi) {
 | 
			
		||||
                exist = true;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (!exist) groupVector.push_back(gi);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // group volumes and their slices according to the grouping Vector
 | 
			
		||||
    groups.clear();
 | 
			
		||||
 | 
			
		||||
    for (int gv : groupVector) {
 | 
			
		||||
        groupedVolumeSlices gvs;
 | 
			
		||||
        gvs.groupId = gv;
 | 
			
		||||
        for (int i = 0; i != objSliceByVolume.size(); ++i) {
 | 
			
		||||
            if (groupIndex[i] == gv) {
 | 
			
		||||
                gvs.volume_ids.push_back(objSliceByVolume[i].volume_id);
 | 
			
		||||
                append(gvs.slices, objSliceByVolume[i].slices[firstLayerReplacedBy]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // the slices of a group should be unioned
 | 
			
		||||
        gvs.slices = offset_ex(union_ex(gvs.slices), -offsetValue);
 | 
			
		||||
        for (ExPolygon& poly_ex : gvs.slices)
 | 
			
		||||
            poly_ex.douglas_peucker(resolution);
 | 
			
		||||
 | 
			
		||||
        groups.push_back(gvs);
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//BBS: filter the members of "objSliceByVolume" such that only "model_part" are included
 | 
			
		||||
std::vector<VolumeSlices> findPartVolumes(const std::vector<VolumeSlices>& objSliceByVolume, ModelVolumePtrs model_volumes) {
 | 
			
		||||
    std::vector<VolumeSlices> outPut;
 | 
			
		||||
    for (const auto& vs : objSliceByVolume) {
 | 
			
		||||
        for (const auto& mv : model_volumes) {
 | 
			
		||||
            if (vs.volume_id == mv->id() && mv->is_model_part()) outPut.push_back(vs);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return outPut;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void applyNegtiveVolumes(ModelVolumePtrs model_volumes, const std::vector<VolumeSlices>& objSliceByVolume, std::vector<groupedVolumeSlices>& groups, double resolution) {
 | 
			
		||||
    ExPolygons negTotal;
 | 
			
		||||
    for (const auto& vs : objSliceByVolume) {
 | 
			
		||||
        for (const auto& mv : model_volumes) {
 | 
			
		||||
            if (vs.volume_id == mv->id() && mv->is_negative_volume()) {
 | 
			
		||||
                if (vs.slices.size() > 0) {
 | 
			
		||||
                    append(negTotal, vs.slices.front());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (auto& g : groups) {
 | 
			
		||||
        g.slices = diff_ex(g.slices, negTotal);
 | 
			
		||||
        for (ExPolygon& poly_ex : g.slices)
 | 
			
		||||
            poly_ex.douglas_peucker(resolution);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void reGroupingLayerPolygons(std::vector<groupedVolumeSlices>& gvss, ExPolygons eps)
 | 
			
		||||
{
 | 
			
		||||
    std::vector<int> epsIndex;
 | 
			
		||||
    epsIndex.resize(eps.size(), -1);
 | 
			
		||||
    for (int ie = 0; ie != eps.size(); ie++) {
 | 
			
		||||
        for (int iv = 0; iv != gvss.size(); iv++) {
 | 
			
		||||
            auto clipedExPolys = diff_ex(eps[ie], gvss[iv].slices);
 | 
			
		||||
            double area = 0;
 | 
			
		||||
            for (const auto& ce : clipedExPolys) {
 | 
			
		||||
                area += ce.area();
 | 
			
		||||
            }
 | 
			
		||||
            if (eps[ie].area() > 0 && area / eps[ie].area() < 0.3) {
 | 
			
		||||
                epsIndex[ie] = iv;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (int iv = 0; iv != gvss.size(); iv++)
 | 
			
		||||
        gvss[iv].slices.clear();
 | 
			
		||||
 | 
			
		||||
    for (int ie = 0; ie != eps.size(); ie++) {
 | 
			
		||||
        if (epsIndex[ie] >= 0)
 | 
			
		||||
            gvss[epsIndex[ie]].slices.push_back(eps[ie]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string fix_slicing_errors(PrintObject* object, LayerPtrs &layers, const std::function<void()> &throw_if_canceled)
 | 
			
		||||
{
 | 
			
		||||
    std::string error_msg;//BBS
 | 
			
		||||
| 
						 | 
				
			
			@ -567,6 +712,21 @@ std::string fix_slicing_errors(PrintObject* object, LayerPtrs &layers, const std
 | 
			
		|||
    //BBS
 | 
			
		||||
    if(error_msg.empty() && !buggy_layers.empty())
 | 
			
		||||
        error_msg = L("The model has too many empty layers.");
 | 
			
		||||
 | 
			
		||||
    // BBS: first layer slices are sorted by volume group, if the first layer is empty and replaced by the 2nd layer
 | 
			
		||||
// the later will be stored in "object->firstLayerObjGroupsMod()"
 | 
			
		||||
    int firstLayerReplacedBy = 0;
 | 
			
		||||
    if (!buggy_layers.empty() && buggy_layers.front() == 0)
 | 
			
		||||
        firstLayerReplacedBy = 1;
 | 
			
		||||
 | 
			
		||||
    const auto           scaled_resolution = scaled<double>(object->print()->config().resolution.value);
 | 
			
		||||
    auto partsObjSliceByVolume = findPartVolumes(object->firstLayerObjSliceMod(), object->model_object()->volumes);
 | 
			
		||||
    groupingVolumes(partsObjSliceByVolume, object->firstLayerObjGroupsMod(), scaled_resolution, firstLayerReplacedBy);
 | 
			
		||||
    applyNegtiveVolumes(object->model_object()->volumes, object->firstLayerObjSliceMod(), object->firstLayerObjGroupsMod(), scaled_resolution);
 | 
			
		||||
 | 
			
		||||
    // BBS: the actual first layer slices stored in layers are re-sorted by volume group and will be used to generate brim
 | 
			
		||||
    reGroupingLayerPolygons(object->firstLayerObjGroupsMod(), layers.front()->lslices);
 | 
			
		||||
 | 
			
		||||
    return error_msg;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -616,7 +776,7 @@ void PrintObject::slice()
 | 
			
		|||
            }
 | 
			
		||||
        });
 | 
			
		||||
    if (m_layers.empty())
 | 
			
		||||
        throw Slic3r::SlicingError("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n");
 | 
			
		||||
        throw Slic3r::SlicingError(L("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"));
 | 
			
		||||
 | 
			
		||||
    // BBS
 | 
			
		||||
    this->set_done(posSlice);
 | 
			
		||||
| 
						 | 
				
			
			@ -749,127 +909,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
 | 
			
		|||
        });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//BBS: justify whether a volume is connected to another one
 | 
			
		||||
bool doesVolumeIntersect(VolumeSlices& vs1, VolumeSlices& vs2)
 | 
			
		||||
{
 | 
			
		||||
    if (vs1.volume_id == vs2.volume_id) return true;
 | 
			
		||||
    if (vs1.slices.size() != vs2.slices.size()) return false;
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i != vs1.slices.size(); ++i) {
 | 
			
		||||
 | 
			
		||||
        if (vs1.slices[i].empty()) continue;
 | 
			
		||||
        if (!vs2.slices[i].empty() && !intersection_ex(vs1.slices[i], vs2.slices[i]).empty()) return true;
 | 
			
		||||
        if (i + 1 != vs2.slices.size() && !vs2.slices[i + 1].empty()) {
 | 
			
		||||
            if (!intersection_ex(vs1.slices[i], vs2.slices[i + 1]).empty()) return true;
 | 
			
		||||
        }
 | 
			
		||||
        if (i - 1 >= 0 && !vs2.slices[i - 1].empty()) {
 | 
			
		||||
            if (!intersection_ex(vs1.slices[i], vs2.slices[i - 1]).empty()) return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//BBS: grouping the volumes of an object according to their connection relationship
 | 
			
		||||
bool groupingVolumes(std::vector<VolumeSlices> objSliceByVolume, std::vector<groupedVolumeSlices>& groups, double resolution)
 | 
			
		||||
{
 | 
			
		||||
    int existGroups = 0;
 | 
			
		||||
    std::vector<int> groupIndex(objSliceByVolume.size(), -1);
 | 
			
		||||
 | 
			
		||||
    double offsetValue = 0.15 / SCALING_FACTOR;
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i != objSliceByVolume.size(); ++i) {
 | 
			
		||||
        for (int j = 0; j != objSliceByVolume[i].slices.size(); ++j) {
 | 
			
		||||
            objSliceByVolume[i].slices[j] = offset_ex(objSliceByVolume[i].slices[j], offsetValue);
 | 
			
		||||
            for (ExPolygon& poly_ex : objSliceByVolume[i].slices[j])
 | 
			
		||||
                poly_ex.douglas_peucker(resolution);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i != objSliceByVolume.size(); ++i) {
 | 
			
		||||
        if (groupIndex[i] < 0) {
 | 
			
		||||
            groupIndex[i] = i;
 | 
			
		||||
            ++existGroups;
 | 
			
		||||
        }
 | 
			
		||||
        for (int j = i + 1; j != objSliceByVolume.size(); ++j) {
 | 
			
		||||
            if (doesVolumeIntersect(objSliceByVolume[i], objSliceByVolume[j])) {
 | 
			
		||||
                if (groupIndex[j] < 0) groupIndex[j] = groupIndex[i];
 | 
			
		||||
                if (groupIndex[j] != groupIndex[i]) {
 | 
			
		||||
                    int retain = std::min(groupIndex[i], groupIndex[j]);
 | 
			
		||||
                    int cover = std::max(groupIndex[i], groupIndex[j]);
 | 
			
		||||
                    for (int k = 0; k != objSliceByVolume.size(); ++k) {
 | 
			
		||||
                        if (groupIndex[k] == cover) groupIndex[k] = retain;
 | 
			
		||||
                    }
 | 
			
		||||
                    --existGroups;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::vector<int> groupVector{};
 | 
			
		||||
    for (int gi : groupIndex) {
 | 
			
		||||
        bool exist = false;
 | 
			
		||||
        for (int gv : groupVector) {
 | 
			
		||||
            if (gv == gi) {
 | 
			
		||||
                exist = true;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (!exist) groupVector.push_back(gi);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // group volumes and their slices according to the grouping Vector
 | 
			
		||||
    groups.clear();
 | 
			
		||||
 | 
			
		||||
    for (int gv : groupVector) {
 | 
			
		||||
        groupedVolumeSlices gvs;
 | 
			
		||||
        gvs.groupId = gv;
 | 
			
		||||
        for (int i = 0; i != objSliceByVolume.size(); ++i) {
 | 
			
		||||
            if (groupIndex[i] == gv) {
 | 
			
		||||
                gvs.volume_ids.push_back(objSliceByVolume[i].volume_id);
 | 
			
		||||
                append(gvs.slices, objSliceByVolume[i].slices.front());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // the slices of a group should be unioned
 | 
			
		||||
        gvs.slices = offset_ex(union_ex(gvs.slices), -offsetValue);
 | 
			
		||||
        for (ExPolygon& poly_ex : gvs.slices)
 | 
			
		||||
            poly_ex.douglas_peucker(resolution);
 | 
			
		||||
 | 
			
		||||
        groups.push_back(gvs);
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//BBS: filter the members of "objSliceByVolume" such that only "model_part" are included
 | 
			
		||||
std::vector<VolumeSlices> findPartVolumes(const std::vector<VolumeSlices>& objSliceByVolume, ModelVolumePtrs model_volumes) {
 | 
			
		||||
    std::vector<VolumeSlices> outPut;
 | 
			
		||||
    for (const auto& vs : objSliceByVolume) {
 | 
			
		||||
        for (const auto& mv : model_volumes) {
 | 
			
		||||
            if (vs.volume_id == mv->id() && mv->is_model_part()) outPut.push_back(vs);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return outPut;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void applyNegtiveVolumes(ModelVolumePtrs model_volumes, const std::vector<VolumeSlices>& objSliceByVolume, std::vector<groupedVolumeSlices>& groups, double resolution) {
 | 
			
		||||
    ExPolygons negTotal;
 | 
			
		||||
    for (const auto& vs : objSliceByVolume) {
 | 
			
		||||
        for (const auto& mv : model_volumes) {
 | 
			
		||||
            if (vs.volume_id == mv->id() && mv->is_negative_volume()) {
 | 
			
		||||
                if (vs.slices.size() > 0) {
 | 
			
		||||
                    append(negTotal, vs.slices.front());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (auto& g : groups) {
 | 
			
		||||
        g.slices = diff_ex(g.slices, negTotal);
 | 
			
		||||
        for (ExPolygon& poly_ex : g.slices)
 | 
			
		||||
            poly_ex.douglas_peucker(resolution);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
// 1) Decides Z positions of the layers,
 | 
			
		||||
// 2) Initializes layers and their regions
 | 
			
		||||
// 3) Slices the object meshes
 | 
			
		||||
| 
						 | 
				
			
			@ -897,14 +937,8 @@ void PrintObject::slice_volumes()
 | 
			
		|||
        for (const std::unique_ptr<PrintRegion> &pr : m_shared_regions->all_regions)
 | 
			
		||||
            layer->m_regions.emplace_back(new LayerRegion(layer, pr.get()));
 | 
			
		||||
    }
 | 
			
		||||
    // BBS: first layer slices are sorted by volume
 | 
			
		||||
    std::vector<float>                   slice_zs      = zs_from_layers(m_layers);
 | 
			
		||||
    if (!slice_zs.empty()) {
 | 
			
		||||
        firstLayerObjSliceByVolume = slice_volumes_inner(
 | 
			
		||||
            print->config(), this->config(), this->trafo_centered(),
 | 
			
		||||
            this->model_object()->volumes, m_shared_regions->layer_ranges, {slice_zs.front()}, throw_on_cancel_callback);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::vector<float>                   slice_zs      = zs_from_layers(m_layers);
 | 
			
		||||
    std::vector<VolumeSlices> objSliceByVolume;
 | 
			
		||||
    if (!slice_zs.empty()) {
 | 
			
		||||
        objSliceByVolume = slice_volumes_inner(
 | 
			
		||||
| 
						 | 
				
			
			@ -913,10 +947,11 @@ void PrintObject::slice_volumes()
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    //BBS: "model_part" volumes are grouded according to their connections
 | 
			
		||||
    const auto           scaled_resolution = scaled<double>(print->config().resolution.value);
 | 
			
		||||
    std::vector<VolumeSlices> objSliceByVolumeParts = findPartVolumes(objSliceByVolume, this->model_object()->volumes);
 | 
			
		||||
    groupingVolumes(objSliceByVolumeParts, firstLayerObjSliceByGroups, scaled_resolution);
 | 
			
		||||
    applyNegtiveVolumes(this->model_object()->volumes, objSliceByVolume, firstLayerObjSliceByGroups, scaled_resolution);
 | 
			
		||||
    //const auto           scaled_resolution = scaled<double>(print->config().resolution.value);
 | 
			
		||||
    //firstLayerObjSliceByVolume = findPartVolumes(objSliceByVolume, this->model_object()->volumes);
 | 
			
		||||
    //groupingVolumes(objSliceByVolumeParts, firstLayerObjSliceByGroups, scaled_resolution);
 | 
			
		||||
    //applyNegtiveVolumes(this->model_object()->volumes, objSliceByVolume, firstLayerObjSliceByGroups, scaled_resolution);
 | 
			
		||||
    firstLayerObjSliceByVolume = objSliceByVolume;
 | 
			
		||||
 | 
			
		||||
    std::vector<std::vector<ExPolygons>> region_slices =
 | 
			
		||||
        slices_to_regions(print->config(), *this, this->model_object()->volumes, *m_shared_regions, slice_zs,
 | 
			
		||||
| 
						 | 
				
			
			@ -949,10 +984,10 @@ void PrintObject::slice_volumes()
 | 
			
		|||
        // If XY Size compensation is also enabled, notify the user that XY Size compensation
 | 
			
		||||
        // would not be used because the object is multi-material painted.
 | 
			
		||||
        if (m_config.xy_hole_compensation.value != 0.f || m_config.xy_contour_compensation.value != 0.f) {
 | 
			
		||||
            //this->active_step_add_warning(
 | 
			
		||||
            //    PrintStateBase::WarningLevel::CRITICAL,
 | 
			
		||||
            //    L("An object has enabled XY Size compensation which will not be used because it is also multi-material painted.\nXY Size "
 | 
			
		||||
            //      "compensation cannot be combined with multi-material painting."));
 | 
			
		||||
            this->active_step_add_warning(
 | 
			
		||||
                PrintStateBase::WarningLevel::CRITICAL,
 | 
			
		||||
                L("An object's XY size compensation will not be used because it is also color-painted.\nXY Size "
 | 
			
		||||
                  "compensation can not be combined with color-painting."));
 | 
			
		||||
            BOOST_LOG_TRIVIAL(info) << "xy compensation will not work for object " << this->model_object()->name << " for multi filament.";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1482,7 +1482,7 @@ static const double length_thresh_well_supported = scale_(6);  // min: 6mm
 | 
			
		|||
static const double area_thresh_well_supported = SQ(length_thresh_well_supported);  // min: 6x6=36mm^2
 | 
			
		||||
static const double sharp_tail_xy_gap = 0.2f;
 | 
			
		||||
static const double no_overlap_xy_gap = 0.2f;
 | 
			
		||||
static const double sharp_tail_max_support_height = 8.f;
 | 
			
		||||
static const double sharp_tail_max_support_height = 16.f;
 | 
			
		||||
 | 
			
		||||
// Tuple: overhang_polygons, contact_polygons, enforcer_polygons, no_interface_offset
 | 
			
		||||
// no_interface_offset: minimum of external perimeter widths
 | 
			
		||||
| 
						 | 
				
			
			@ -1598,7 +1598,7 @@ static inline Polygons detect_overhangs(
 | 
			
		|||
                        // Check whether this is a sharp tail region.
 | 
			
		||||
                        // Should use lower_layer_expolys without any offset. Otherwise, it may missing sharp tails near the main body.
 | 
			
		||||
                        if (intersection_ex({ expoly }, lower_layer_expolys).empty()) {
 | 
			
		||||
                            is_sharp_tail = expoly.area() < area_thresh_well_supported;
 | 
			
		||||
                            is_sharp_tail = expoly.area() < area_thresh_well_supported && !offset_ex(expoly,-0.5*fw).empty();
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1646,7 +1646,8 @@ static inline Polygons detect_overhangs(
 | 
			
		|||
                        // 2.4 if the area grows fast than threshold, it get connected to other part or
 | 
			
		||||
                        // it has a sharp slop and will be auto supported.
 | 
			
		||||
                        ExPolygons new_overhang_expolys = diff_ex({ expoly }, lower_layer_sharptails);
 | 
			
		||||
                        if (!offset_ex(new_overhang_expolys, -5.0 * fw).empty()) {
 | 
			
		||||
                        Point      size_diff            = get_extents(new_overhang_expolys).size() - get_extents(lower_layer_sharptails).size();
 | 
			
		||||
                        if (size_diff.both_comp(Point(scale_(5),scale_(5)),">") || !offset_ex(new_overhang_expolys, -5.0 * fw).empty()) {
 | 
			
		||||
                            is_sharp_tail = false;
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,7 +27,7 @@
 | 
			
		|||
#define TAU (2.0 * M_PI)
 | 
			
		||||
#define NO_INDEX (std::numeric_limits<unsigned int>::max())
 | 
			
		||||
 | 
			
		||||
//#define SUPPORT_TREE_DEBUG_TO_SVG
 | 
			
		||||
// #define SUPPORT_TREE_DEBUG_TO_SVG
 | 
			
		||||
 | 
			
		||||
namespace Slic3r
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -285,7 +285,7 @@ static void draw_layer_mst
 | 
			
		|||
        svg.draw(spanning_tree.vertices(), "black", coord_t(scale_(0.1)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void draw_two_overhangs_to_svg(TreeSupportLayer* ts_layer, const ExPolygons& overhangs1, const ExPolygons& overhangs2)
 | 
			
		||||
static void draw_two_overhangs_to_svg(SupportLayer* ts_layer, const ExPolygons& overhangs1, const ExPolygons& overhangs2)
 | 
			
		||||
{
 | 
			
		||||
    if (overhangs1.empty() && overhangs2.empty())
 | 
			
		||||
        return;
 | 
			
		||||
| 
						 | 
				
			
			@ -300,7 +300,7 @@ static void draw_two_overhangs_to_svg(TreeSupportLayer* ts_layer, const ExPolygo
 | 
			
		|||
    svg.draw(union_ex(overhangs2), "red");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void draw_polylines(TreeSupportLayer* ts_layer, Polylines& polylines)
 | 
			
		||||
static void draw_polylines(SupportLayer* ts_layer, Polylines& polylines)
 | 
			
		||||
{
 | 
			
		||||
    if (polylines.empty())
 | 
			
		||||
        return;
 | 
			
		||||
| 
						 | 
				
			
			@ -698,32 +698,41 @@ TreeSupport::TreeSupport(PrintObject& object, const SlicingParameters &slicing_p
 | 
			
		|||
                                                  ipConcentric :
 | 
			
		||||
                                                  (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase);
 | 
			
		||||
    m_support_params.support_extrusion_width = m_object_config->support_line_width.value > 0 ? m_object_config->support_line_width : m_object_config->line_width;
 | 
			
		||||
    is_slim                                  = is_tree_slim(m_object_config->support_type, m_object_config->support_style);
 | 
			
		||||
    MAX_BRANCH_RADIUS                        = is_slim ? 5.0 : 10.0;
 | 
			
		||||
    support_type                             = m_object_config->support_type;
 | 
			
		||||
    is_slim                                  = is_tree_slim(support_type, m_object_config->support_style);
 | 
			
		||||
    MAX_BRANCH_RADIUS                        = 10.0;
 | 
			
		||||
    tree_support_branch_diameter_angle       = 5.0;//is_slim ? 10.0 : 5.0;
 | 
			
		||||
    // by default tree support needs no infill, unless it's tree hybrid which contains normal nodes.
 | 
			
		||||
    with_infill                              = support_pattern != smpNone && support_pattern != smpDefault;
 | 
			
		||||
    const PrintConfig& print_config = m_object->print()->config();
 | 
			
		||||
    m_machine_border.contour = get_bed_shape_with_excluded_area(print_config);
 | 
			
		||||
    Vec3d plate_offset       = m_object->print()->get_plate_origin();
 | 
			
		||||
    // align with the centered object in current plate (may not be the 1st plate, so need to add the plate offset)
 | 
			
		||||
    m_machine_border.translate(Point(scale_(plate_offset(0)), scale_(plate_offset(1))) - m_object->instances().front().shift);
 | 
			
		||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
 | 
			
		||||
    SVG svg("SVG/machine_boarder.svg", m_object->bounding_box());
 | 
			
		||||
    if (svg.is_opened()) svg.draw(m_machine_border, "yellow");
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
 | 
			
		||||
void TreeSupport::detect_object_overhangs()
 | 
			
		||||
void TreeSupport::detect_overhangs()
 | 
			
		||||
{
 | 
			
		||||
    // overhangs are already detected
 | 
			
		||||
    if (m_object->tree_support_layer_count() >= m_object->layer_count())
 | 
			
		||||
    if (m_object->support_layer_count() >= m_object->layer_count())
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    // Clear and create Tree Support Layers
 | 
			
		||||
    m_object->clear_tree_support_layers();
 | 
			
		||||
    m_object->clear_support_layers();
 | 
			
		||||
    m_object->clear_tree_support_preview_cache();
 | 
			
		||||
 | 
			
		||||
    create_tree_support_layers();
 | 
			
		||||
    m_ts_data = m_object->alloc_tree_support_preview_cache();
 | 
			
		||||
    m_ts_data->is_slim = is_slim;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    const PrintObjectConfig& config = m_object->config();
 | 
			
		||||
    SupportType stype = config.support_type.value;
 | 
			
		||||
    const coordf_t radius_sample_resolution = m_ts_data->m_radius_sample_resolution;
 | 
			
		||||
    SupportType stype = support_type;
 | 
			
		||||
    const coordf_t radius_sample_resolution = g_config_tree_support_collision_resolution;
 | 
			
		||||
    const coordf_t extrusion_width = config.line_width.value;
 | 
			
		||||
    const coordf_t extrusion_width_scaled = scale_(extrusion_width);
 | 
			
		||||
    const coordf_t max_bridge_length = scale_(config.max_bridge_length.value);
 | 
			
		||||
| 
						 | 
				
			
			@ -732,7 +741,7 @@ void TreeSupport::detect_object_overhangs()
 | 
			
		|||
    const int enforce_support_layers = config.enforce_support_layers.value;
 | 
			
		||||
    const double area_thresh_well_supported = SQ(scale_(6));
 | 
			
		||||
    const double length_thresh_well_supported = scale_(6);
 | 
			
		||||
    static const double sharp_tail_max_support_height = 8.f;
 | 
			
		||||
    static const double sharp_tail_max_support_height = 16.f;
 | 
			
		||||
    // a region is considered well supported if the number of layers below it exceeds this threshold
 | 
			
		||||
    const int thresh_layers_below = 10 / config.layer_height;
 | 
			
		||||
    double obj_height = m_object->size().z();
 | 
			
		||||
| 
						 | 
				
			
			@ -922,9 +931,9 @@ void TreeSupport::detect_object_overhangs()
 | 
			
		|||
                    float accum_height  = layer->height;
 | 
			
		||||
                    do {
 | 
			
		||||
                        // 1. nothing below
 | 
			
		||||
                        // check whether this is a sharp tail region
 | 
			
		||||
                        // this is a sharp tail region if it's small but non-ignorable
 | 
			
		||||
                        if (intersection_ex({expoly}, lower_polys).empty()) {
 | 
			
		||||
                            is_sharp_tail = expoly.area() < area_thresh_well_supported;
 | 
			
		||||
                            is_sharp_tail = expoly.area() < area_thresh_well_supported && !offset_ex(expoly,-0.5*extrusion_width_scaled).empty();
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -968,7 +977,7 @@ void TreeSupport::detect_object_overhangs()
 | 
			
		|||
                        // 2.4 if the area grows fast than threshold, it get connected to other part or
 | 
			
		||||
                        // it has a sharp slop and will be auto supported.
 | 
			
		||||
                        ExPolygons new_overhang_expolys = diff_ex({expoly}, lower_layer_sharptails);
 | 
			
		||||
                        if (!offset_ex(new_overhang_expolys, -5.0 * extrusion_width_scaled).empty()) {
 | 
			
		||||
                        if ((get_extents(new_overhang_expolys).size()-get_extents(lower_layer_sharptails).size()).both_comp(Point(scale_(5),scale_(5)),">") || !offset_ex(new_overhang_expolys, -5.0 * extrusion_width_scaled).empty()) {
 | 
			
		||||
                            is_sharp_tail = false;
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
| 
						 | 
				
			
			@ -982,6 +991,9 @@ void TreeSupport::detect_object_overhangs()
 | 
			
		|||
                        layer->sharp_tails.push_back(expoly);
 | 
			
		||||
                        layer->sharp_tails_height.insert({ &expoly, accum_height });
 | 
			
		||||
                        append(overhang_areas, overhang);
 | 
			
		||||
 | 
			
		||||
                        if (!overhang.empty())
 | 
			
		||||
                            has_sharp_tails = true;
 | 
			
		||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
 | 
			
		||||
                        SVG svg(get_svg_filename(std::to_string(layer->print_z), "sharp_tail"), m_object->bounding_box());
 | 
			
		||||
                        if (svg.is_opened()) svg.draw(overhang, "yellow");
 | 
			
		||||
| 
						 | 
				
			
			@ -992,11 +1004,13 @@ void TreeSupport::detect_object_overhangs()
 | 
			
		|||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            if (bridge_no_support && overhang_areas.size()>0) {
 | 
			
		||||
                m_object->remove_bridges_from_contacts(lower_layer, layer, extrusion_width_scaled, &overhang_areas, max_bridge_length, true);
 | 
			
		||||
            if (max_bridge_length > 0 && overhang_areas.size()>0) {
 | 
			
		||||
                // do not break bridge for normal part in TreeHybrid
 | 
			
		||||
                bool break_bridge = !(config.support_style == smsTreeHybrid && area(overhang_areas) > m_support_params.thresh_big_overhang);
 | 
			
		||||
                m_object->remove_bridges_from_contacts(lower_layer, layer, extrusion_width_scaled, &overhang_areas, max_bridge_length, break_bridge);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
 | 
			
		||||
            SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers);
 | 
			
		||||
            for (ExPolygon& poly : overhang_areas) {
 | 
			
		||||
                if (!offset_ex(poly, -0.1 * extrusion_width_scaled).empty())
 | 
			
		||||
                    ts_layer->overhang_areas.emplace_back(poly);
 | 
			
		||||
| 
						 | 
				
			
			@ -1146,9 +1160,9 @@ void TreeSupport::detect_object_overhangs()
 | 
			
		|||
        if (m_object->print()->canceled())
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
 | 
			
		||||
        SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers);
 | 
			
		||||
        auto layer = m_object->get_layer(layer_nr);
 | 
			
		||||
        if (support_critical_regions_only) {
 | 
			
		||||
            auto layer = m_object->get_layer(layer_nr);
 | 
			
		||||
            auto lower_layer = layer->lower_layer;
 | 
			
		||||
            if (lower_layer == nullptr)
 | 
			
		||||
                ts_layer->overhang_areas = layer->sharp_tails;
 | 
			
		||||
| 
						 | 
				
			
			@ -1164,7 +1178,7 @@ void TreeSupport::detect_object_overhangs()
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        for (auto &area : ts_layer->overhang_areas) {
 | 
			
		||||
            ts_layer->overhang_types.emplace(&area, TreeSupportLayer::Detected);
 | 
			
		||||
            ts_layer->overhang_types.emplace(&area, SupportLayer::Detected);
 | 
			
		||||
        }
 | 
			
		||||
        // enforcers
 | 
			
		||||
        if (layer_nr < enforcers.size()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1175,15 +1189,16 @@ void TreeSupport::detect_object_overhangs()
 | 
			
		|||
            enforcer = offset(enforcer, 0.1 * extrusion_width_scaled);
 | 
			
		||||
            for (const Polygon& poly : enforcer) {
 | 
			
		||||
                ts_layer->overhang_areas.emplace_back(poly);
 | 
			
		||||
                ts_layer->overhang_types.emplace(&ts_layer->overhang_areas.back(), TreeSupportLayer::Enforced);
 | 
			
		||||
                ts_layer->overhang_types.emplace(&ts_layer->overhang_areas.back(), SupportLayer::Enforced);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!ts_layer->overhang_areas.empty()) has_overhangs = true;
 | 
			
		||||
        if (!layer->cantilevers.empty()) has_cantilever = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
 | 
			
		||||
    for (const TreeSupportLayer* layer : m_object->tree_support_layers()) {
 | 
			
		||||
    for (const SupportLayer* layer : m_object->support_layers()) {
 | 
			
		||||
        if (layer->overhang_areas.empty())
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1220,9 +1235,9 @@ void TreeSupport::create_tree_support_layers()
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    for (Layer *layer : m_object->layers()) {
 | 
			
		||||
        TreeSupportLayer* ts_layer = m_object->add_tree_support_layer(layer->id(), layer->height, layer->print_z, layer->slice_z);
 | 
			
		||||
        SupportLayer* ts_layer = m_object->add_tree_support_layer(layer->id(), layer->height, layer->print_z, layer->slice_z);
 | 
			
		||||
        if (ts_layer->id() > m_raft_layers) {
 | 
			
		||||
            TreeSupportLayer* lower_layer = m_object->get_tree_support_layer(ts_layer->id() - 1);
 | 
			
		||||
            SupportLayer* lower_layer = m_object->get_support_layer(ts_layer->id() - 1);
 | 
			
		||||
            lower_layer->upper_layer = ts_layer;
 | 
			
		||||
            ts_layer->lower_layer = lower_layer;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1434,7 +1449,7 @@ void TreeSupport::generate_toolpaths()
 | 
			
		|||
    const coordf_t branch_radius = object_config.tree_support_branch_diameter.value / 2;
 | 
			
		||||
    const coordf_t branch_radius_scaled = scale_(branch_radius);
 | 
			
		||||
 | 
			
		||||
    if (m_object->tree_support_layers().empty())
 | 
			
		||||
    if (m_object->support_layers().empty())
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    // calculate fill areas for raft layers
 | 
			
		||||
| 
						 | 
				
			
			@ -1446,8 +1461,8 @@ void TreeSupport::generate_toolpaths()
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (m_object->tree_support_layer_count() > m_raft_layers) {
 | 
			
		||||
        const TreeSupportLayer *ts_layer = m_object->get_tree_support_layer(m_raft_layers);
 | 
			
		||||
    if (m_object->support_layer_count() > m_raft_layers) {
 | 
			
		||||
        const SupportLayer *ts_layer = m_object->get_support_layer(m_raft_layers);
 | 
			
		||||
        for (const ExPolygon expoly : ts_layer->floor_areas)
 | 
			
		||||
            raft_areas.push_back(expoly);
 | 
			
		||||
        for (const ExPolygon expoly : ts_layer->roof_areas)
 | 
			
		||||
| 
						 | 
				
			
			@ -1462,7 +1477,7 @@ void TreeSupport::generate_toolpaths()
 | 
			
		|||
    if (m_raft_layers > 0)
 | 
			
		||||
    {
 | 
			
		||||
        ExtrusionRole raft_contour_er = m_slicing_params.base_raft_layers > 0 ? erSupportMaterial : erSupportMaterialInterface;
 | 
			
		||||
        TreeSupportLayer *ts_layer = m_object->tree_support_layers().front();
 | 
			
		||||
        SupportLayer *ts_layer = m_object->support_layers().front();
 | 
			
		||||
        Flow flow = m_object->print()->brim_flow();
 | 
			
		||||
 | 
			
		||||
        Polygons loops;
 | 
			
		||||
| 
						 | 
				
			
			@ -1476,7 +1491,7 @@ void TreeSupport::generate_toolpaths()
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    for (size_t layer_nr = 0; layer_nr < m_slicing_params.base_raft_layers; layer_nr++) {
 | 
			
		||||
        TreeSupportLayer *ts_layer = m_object->get_tree_support_layer(layer_nr);
 | 
			
		||||
        SupportLayer *ts_layer = m_object->get_support_layer(layer_nr);
 | 
			
		||||
        coordf_t expand_offset = (layer_nr == 0 ? 0. : -1.);
 | 
			
		||||
 | 
			
		||||
        Flow support_flow = layer_nr == 0 ? m_object->print()->brim_flow() : Flow(support_extrusion_width, ts_layer->height, nozzle_diameter);
 | 
			
		||||
| 
						 | 
				
			
			@ -1496,7 +1511,7 @@ void TreeSupport::generate_toolpaths()
 | 
			
		|||
         layer_nr < m_slicing_params.base_raft_layers + m_slicing_params.interface_raft_layers;
 | 
			
		||||
         layer_nr++)
 | 
			
		||||
    {
 | 
			
		||||
        TreeSupportLayer *ts_layer = m_object->get_tree_support_layer(layer_nr);
 | 
			
		||||
        SupportLayer *ts_layer = m_object->get_support_layer(layer_nr);
 | 
			
		||||
        coordf_t expand_offset = (layer_nr == 0 ? 0. : -1.);
 | 
			
		||||
 | 
			
		||||
        Flow support_flow(support_extrusion_width, ts_layer->height, nozzle_diameter);
 | 
			
		||||
| 
						 | 
				
			
			@ -1528,7 +1543,7 @@ void TreeSupport::generate_toolpaths()
 | 
			
		|||
 | 
			
		||||
    // generate tree support tool paths
 | 
			
		||||
    tbb::parallel_for(
 | 
			
		||||
        tbb::blocked_range<size_t>(m_raft_layers, m_object->tree_support_layer_count()),
 | 
			
		||||
        tbb::blocked_range<size_t>(m_raft_layers, m_object->support_layer_count()),
 | 
			
		||||
        [&](const tbb::blocked_range<size_t>& range)
 | 
			
		||||
        {
 | 
			
		||||
            for (size_t layer_id = range.begin(); layer_id < range.end(); layer_id++) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1537,7 +1552,7 @@ void TreeSupport::generate_toolpaths()
 | 
			
		|||
 | 
			
		||||
                m_object->print()->set_status(70, (boost::format(_L("Support: generate toolpath at layer %d")) % layer_id).str());
 | 
			
		||||
 | 
			
		||||
                TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_id);
 | 
			
		||||
                SupportLayer* ts_layer = m_object->get_support_layer(layer_id);
 | 
			
		||||
                Flow support_flow(support_extrusion_width, ts_layer->height, nozzle_diameter);
 | 
			
		||||
                coordf_t support_spacing         = object_config.support_base_pattern_spacing.value + support_flow.spacing();
 | 
			
		||||
                coordf_t support_density         = std::min(1., support_flow.spacing() / support_spacing);
 | 
			
		||||
| 
						 | 
				
			
			@ -1548,14 +1563,14 @@ void TreeSupport::generate_toolpaths()
 | 
			
		|||
                    ExPolygon& poly = *area_group.area;
 | 
			
		||||
                    ExPolygons polys;
 | 
			
		||||
                    FillParams fill_params;
 | 
			
		||||
                    if (area_group.type != TreeSupportLayer::BaseType) {
 | 
			
		||||
                    if (area_group.type != SupportLayer::BaseType) {
 | 
			
		||||
                        // interface
 | 
			
		||||
                        if (layer_id == 0) {
 | 
			
		||||
                            Flow flow = m_raft_layers == 0 ? m_object->print()->brim_flow() : support_flow;
 | 
			
		||||
                            make_perimeter_and_inner_brim(ts_layer->support_fills.entities, poly, wall_count, flow,
 | 
			
		||||
                                                          area_group.type == TreeSupportLayer::RoofType ? erSupportMaterialInterface : erSupportMaterial);
 | 
			
		||||
                                                          area_group.type == SupportLayer::RoofType ? erSupportMaterialInterface : erSupportMaterial);
 | 
			
		||||
                            polys = std::move(offset_ex(poly, -flow.scaled_spacing()));
 | 
			
		||||
                        } else if (area_group.type == TreeSupportLayer::Roof1stLayer) {
 | 
			
		||||
                        } else if (area_group.type == SupportLayer::Roof1stLayer) {
 | 
			
		||||
                            polys = std::move(offset_ex(poly, 0.5*support_flow.scaled_width()));
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
| 
						 | 
				
			
			@ -1564,7 +1579,7 @@ void TreeSupport::generate_toolpaths()
 | 
			
		|||
                        fill_params.density = interface_density;
 | 
			
		||||
                        fill_params.dont_adjust = true;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (area_group.type == TreeSupportLayer::Roof1stLayer) {
 | 
			
		||||
                    if (area_group.type == SupportLayer::Roof1stLayer) {
 | 
			
		||||
                        // roof_1st_layer
 | 
			
		||||
                        fill_params.density = interface_density;
 | 
			
		||||
                        // Note: spacing means the separation between two lines as if they are tightly extruded
 | 
			
		||||
| 
						 | 
				
			
			@ -1578,13 +1593,13 @@ void TreeSupport::generate_toolpaths()
 | 
			
		|||
                            ts_layer->support_fills.entities.push_back(temp_support_fills);
 | 
			
		||||
                        else
 | 
			
		||||
                            delete temp_support_fills;
 | 
			
		||||
                    } else if (area_group.type == TreeSupportLayer::FloorType) {
 | 
			
		||||
                    } else if (area_group.type == SupportLayer::FloorType) {
 | 
			
		||||
                        // floor_areas
 | 
			
		||||
                        fill_params.density = bottom_interface_density;
 | 
			
		||||
                        filler_interface->spacing = m_support_material_interface_flow.spacing();
 | 
			
		||||
                        fill_expolygons_generate_paths(ts_layer->support_fills.entities, std::move(polys),
 | 
			
		||||
                            filler_interface.get(), fill_params, erSupportMaterialInterface, m_support_material_interface_flow);
 | 
			
		||||
                    } else if (area_group.type == TreeSupportLayer::RoofType) {
 | 
			
		||||
                    } else if (area_group.type == SupportLayer::RoofType) {
 | 
			
		||||
                        // roof_areas
 | 
			
		||||
                        fill_params.density       = interface_density;
 | 
			
		||||
                        filler_interface->spacing = m_support_material_interface_flow.spacing();
 | 
			
		||||
| 
						 | 
				
			
			@ -1886,7 +1901,7 @@ Polygons TreeSupport::contact_nodes_to_polygon(const std::vector<Node*>& contact
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void TreeSupport::generate_support_areas()
 | 
			
		||||
void TreeSupport::generate()
 | 
			
		||||
{
 | 
			
		||||
    bool tree_support_enable = m_object_config->enable_support.value && is_tree(m_object_config->support_type.value);
 | 
			
		||||
    if (!tree_support_enable)
 | 
			
		||||
| 
						 | 
				
			
			@ -1899,9 +1914,14 @@ void TreeSupport::generate_support_areas()
 | 
			
		|||
    // Generate overhang areas
 | 
			
		||||
    profiler.stage_start(STAGE_DETECT_OVERHANGS);
 | 
			
		||||
    m_object->print()->set_status(55, _L("Support: detect overhangs"));
 | 
			
		||||
    detect_object_overhangs();
 | 
			
		||||
    detect_overhangs();
 | 
			
		||||
    profiler.stage_finish(STAGE_DETECT_OVERHANGS);
 | 
			
		||||
 | 
			
		||||
    if (!has_overhangs) return;
 | 
			
		||||
 | 
			
		||||
    m_ts_data = m_object->alloc_tree_support_preview_cache();
 | 
			
		||||
    m_ts_data->is_slim = is_slim;
 | 
			
		||||
 | 
			
		||||
    // Generate contact points of tree support
 | 
			
		||||
    profiler.stage_start(STAGE_GENERATE_CONTACT_NODES);
 | 
			
		||||
    m_object->print()->set_status(56, _L("Support: generate contact points"));
 | 
			
		||||
| 
						 | 
				
			
			@ -2071,7 +2091,7 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
 | 
			
		|||
                    break;
 | 
			
		||||
 | 
			
		||||
                const std::vector<Node*>& curr_layer_nodes = contact_nodes[layer_nr];
 | 
			
		||||
                TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
 | 
			
		||||
                SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers);
 | 
			
		||||
                assert(ts_layer != nullptr);
 | 
			
		||||
 | 
			
		||||
                // skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355)
 | 
			
		||||
| 
						 | 
				
			
			@ -2201,18 +2221,20 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
 | 
			
		|||
                //roof_areas = std::move(diff_ex(roof_areas, avoid_region_interface));
 | 
			
		||||
                //roof_1st_layer = std::move(diff_ex(roof_1st_layer, avoid_region_interface));
 | 
			
		||||
                roof_areas = avoid_object_remove_extra_small_parts(roof_areas, avoid_region_interface);
 | 
			
		||||
                roof_areas = intersection_ex(roof_areas, m_machine_border);
 | 
			
		||||
                roof_1st_layer = avoid_object_remove_extra_small_parts(roof_1st_layer, avoid_region_interface);
 | 
			
		||||
 | 
			
		||||
                // roof_1st_layer and roof_areas may intersect, so need to subtract roof_areas from roof_1st_layer
 | 
			
		||||
                roof_1st_layer = std::move(diff_ex(roof_1st_layer, roof_areas));
 | 
			
		||||
                roof_1st_layer = intersection_ex(roof_1st_layer, m_machine_border);
 | 
			
		||||
 | 
			
		||||
                // let supports touch objects when brim is on
 | 
			
		||||
                auto avoid_region = m_ts_data->get_collision((layer_nr == 0 && has_brim) ? config.brim_object_gap : m_ts_data->m_xy_distance, layer_nr);
 | 
			
		||||
                // base_areas = std::move(diff_ex(base_areas, avoid_region));
 | 
			
		||||
                base_areas = avoid_object_remove_extra_small_parts(base_areas, avoid_region);
 | 
			
		||||
                base_areas = std::move(diff_ex(base_areas, roof_areas));
 | 
			
		||||
                base_areas = std::move(diff_ex(base_areas, roof_1st_layer));
 | 
			
		||||
                base_areas = std::move(diff_ex(base_areas, roof_gap_areas));
 | 
			
		||||
                base_areas = intersection_ex(base_areas, m_machine_border);
 | 
			
		||||
 | 
			
		||||
                if (SQUARE_SUPPORT) {
 | 
			
		||||
                    // simplify support contours
 | 
			
		||||
| 
						 | 
				
			
			@ -2247,10 +2269,10 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
 | 
			
		|||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                auto &area_groups = ts_layer->area_groups;
 | 
			
		||||
                for (auto &area : ts_layer->base_areas) area_groups.emplace_back(&area, TreeSupportLayer::BaseType, max_layers_above_base);
 | 
			
		||||
                for (auto &area : ts_layer->roof_areas) area_groups.emplace_back(&area, TreeSupportLayer::RoofType, max_layers_above_roof);
 | 
			
		||||
                for (auto &area : ts_layer->floor_areas) area_groups.emplace_back(&area, TreeSupportLayer::FloorType, 10000);
 | 
			
		||||
                for (auto &area : ts_layer->roof_1st_layer) area_groups.emplace_back(&area, TreeSupportLayer::Roof1stLayer, max_layers_above_roof1);
 | 
			
		||||
                for (auto &area : ts_layer->base_areas) area_groups.emplace_back(&area, SupportLayer::BaseType, max_layers_above_base);
 | 
			
		||||
                for (auto &area : ts_layer->roof_areas) area_groups.emplace_back(&area, SupportLayer::RoofType, max_layers_above_roof);
 | 
			
		||||
                for (auto &area : ts_layer->floor_areas) area_groups.emplace_back(&area, SupportLayer::FloorType, 10000);
 | 
			
		||||
                for (auto &area : ts_layer->roof_1st_layer) area_groups.emplace_back(&area, SupportLayer::Roof1stLayer, max_layers_above_roof1);
 | 
			
		||||
 | 
			
		||||
                for (auto &area_group : area_groups) {
 | 
			
		||||
                    auto& expoly = area_group.area;
 | 
			
		||||
| 
						 | 
				
			
			@ -2275,7 +2297,7 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
 | 
			
		|||
            for (int layer_nr = 1; layer_nr < m_object->layer_count(); layer_nr++) {
 | 
			
		||||
                if (print->canceled()) break;
 | 
			
		||||
                const std::vector<Node*>& curr_layer_nodes = contact_nodes[layer_nr];
 | 
			
		||||
                TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
 | 
			
		||||
                SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers);
 | 
			
		||||
                assert(ts_layer != nullptr);
 | 
			
		||||
 | 
			
		||||
                // skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355)
 | 
			
		||||
| 
						 | 
				
			
			@ -2287,10 +2309,10 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
 | 
			
		|||
 | 
			
		||||
                int layer_nr_lower = layer_nr - 1;
 | 
			
		||||
                for (layer_nr_lower; layer_nr_lower >= 0; layer_nr_lower--) {
 | 
			
		||||
                    if (!m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers)->area_groups.empty()) break;
 | 
			
		||||
                    if (!m_object->get_support_layer(layer_nr_lower + m_raft_layers)->area_groups.empty()) break;
 | 
			
		||||
                }
 | 
			
		||||
                TreeSupportLayer* lower_layer = m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers);
 | 
			
		||||
                ExPolygons& base_areas_lower = m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers)->base_areas;
 | 
			
		||||
                SupportLayer* lower_layer = m_object->get_support_layer(layer_nr_lower + m_raft_layers);
 | 
			
		||||
                ExPolygons& base_areas_lower = m_object->get_support_layer(layer_nr_lower + m_raft_layers)->base_areas;
 | 
			
		||||
 | 
			
		||||
                ExPolygons overhang;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2324,7 +2346,7 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
 | 
			
		|||
                printZ_to_lightninglayer[lower_layer->print_z] = overhangs.size() - 1;
 | 
			
		||||
 | 
			
		||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
 | 
			
		||||
                draw_two_overhangs_to_svg(m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers), base_areas_lower, to_expolygons(overhangs.back()));
 | 
			
		||||
                draw_two_overhangs_to_svg(m_object->get_support_layer(layer_nr_lower + m_raft_layers), base_areas_lower, to_expolygons(overhangs.back()));
 | 
			
		||||
#endif
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2364,7 +2386,7 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
 | 
			
		|||
                m_object->print()->set_status(66, (boost::format(_L("Support: fix holes at layer %d")) % layer_nr).str());
 | 
			
		||||
 | 
			
		||||
                const std::vector<Node*>& curr_layer_nodes = contact_nodes[layer_nr];
 | 
			
		||||
                TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
 | 
			
		||||
                SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers);
 | 
			
		||||
                assert(ts_layer != nullptr);
 | 
			
		||||
 | 
			
		||||
                // skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355)
 | 
			
		||||
| 
						 | 
				
			
			@ -2374,18 +2396,18 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
 | 
			
		|||
 | 
			
		||||
                int layer_nr_lower = layer_nr - 1;
 | 
			
		||||
                for (layer_nr_lower; layer_nr_lower >= 0; layer_nr_lower--) {
 | 
			
		||||
                    if (!m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers)->area_groups.empty()) break;
 | 
			
		||||
                    if (!m_object->get_support_layer(layer_nr_lower + m_raft_layers)->area_groups.empty()) break;
 | 
			
		||||
                }
 | 
			
		||||
                if (layer_nr_lower < 0) continue;
 | 
			
		||||
                auto& area_groups_lower = m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers)->area_groups;
 | 
			
		||||
                auto& area_groups_lower = m_object->get_support_layer(layer_nr_lower + m_raft_layers)->area_groups;
 | 
			
		||||
 | 
			
		||||
                for (const auto& area_group : ts_layer->area_groups) {
 | 
			
		||||
                    if (area_group.type != TreeSupportLayer::BaseType) continue;
 | 
			
		||||
                    if (area_group.type != SupportLayer::BaseType) continue;
 | 
			
		||||
                    const auto& area = area_group.area;
 | 
			
		||||
                    for (const auto& hole : area->holes) {
 | 
			
		||||
                        // auto hole_bbox = get_extents(hole).polygon();
 | 
			
		||||
                        for (auto& area_group_lower : area_groups_lower) {
 | 
			
		||||
                            if (area_group.type != TreeSupportLayer::BaseType) continue;
 | 
			
		||||
                            if (area_group.type != SupportLayer::BaseType) continue;
 | 
			
		||||
                            auto& base_area_lower = *area_group_lower.area;
 | 
			
		||||
                            Point pt_on_poly, pt_on_expoly, pt_far_on_poly;
 | 
			
		||||
                            // if a hole doesn't intersect with lower layer's contours, add a hole to lower layer and move it slightly to the contour
 | 
			
		||||
| 
						 | 
				
			
			@ -2450,8 +2472,8 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
 | 
			
		|||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
 | 
			
		||||
    for (int layer_nr = m_object->layer_count() - 1; layer_nr > 0; layer_nr--) {
 | 
			
		||||
        TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
 | 
			
		||||
    for (int layer_nr = m_object->layer_count() - 1; layer_nr >= 0; layer_nr--) {
 | 
			
		||||
        SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers);
 | 
			
		||||
        ExPolygons& base_areas = ts_layer->base_areas;
 | 
			
		||||
        ExPolygons& roof_areas = ts_layer->roof_areas;
 | 
			
		||||
        ExPolygons& roof_1st_layer = ts_layer->roof_1st_layer;
 | 
			
		||||
| 
						 | 
				
			
			@ -2466,7 +2488,7 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
 | 
			
		|||
    draw_circles_layer_out.open("./SVG/layer_heights_draw_circles.txt");
 | 
			
		||||
    if (draw_circles_layer_out.is_open()) {
 | 
			
		||||
        for (int layer_nr = m_object->layer_count() - 1; layer_nr > 0; layer_nr--) {
 | 
			
		||||
            TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
 | 
			
		||||
            SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers);
 | 
			
		||||
            ExPolygons& base_areas = ts_layer->base_areas;
 | 
			
		||||
            ExPolygons& roof_areas = ts_layer->roof_areas;
 | 
			
		||||
            ExPolygons& roof_1st_layer = ts_layer->roof_1st_layer;
 | 
			
		||||
| 
						 | 
				
			
			@ -2477,8 +2499,8 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
 | 
			
		|||
    }
 | 
			
		||||
#endif  // SUPPORT_TREE_DEBUG_TO_SVG
 | 
			
		||||
 | 
			
		||||
    TreeSupportLayerPtrs& ts_layers = m_object->tree_support_layers();
 | 
			
		||||
    auto iter = std::remove_if(ts_layers.begin(), ts_layers.end(), [](TreeSupportLayer* ts_layer) { return ts_layer->height < EPSILON; });
 | 
			
		||||
    SupportLayerPtrs& ts_layers = m_object->support_layers();
 | 
			
		||||
    auto iter = std::remove_if(ts_layers.begin(), ts_layers.end(), [](SupportLayer* ts_layer) { return ts_layer->height < EPSILON; });
 | 
			
		||||
    ts_layers.erase(iter, ts_layers.end());
 | 
			
		||||
    for (int layer_nr = 0; layer_nr < ts_layers.size(); layer_nr++) {
 | 
			
		||||
        ts_layers[layer_nr]->upper_layer = layer_nr != ts_layers.size() - 1 ? ts_layers[layer_nr + 1] : nullptr;
 | 
			
		||||
| 
						 | 
				
			
			@ -2527,7 +2549,8 @@ void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
 | 
			
		|||
        return move_dist;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    std::vector<std::pair<coordf_t, coordf_t>> layer_heights = plan_layer_heights(contact_nodes);
 | 
			
		||||
    m_ts_data->layer_heights = plan_layer_heights(contact_nodes);
 | 
			
		||||
    std::vector<LayerHeightData> &layer_heights = m_ts_data->layer_heights;
 | 
			
		||||
    if (layer_heights.empty()) return;
 | 
			
		||||
 | 
			
		||||
    std::unordered_set<Node*> to_free_node_set;
 | 
			
		||||
| 
						 | 
				
			
			@ -2547,9 +2570,10 @@ void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
 | 
			
		|||
            for (Node *p_node : contact_nodes[layer_nr]) {
 | 
			
		||||
                layer_node_dist.emplace(p_node->dist_mm_to_top);
 | 
			
		||||
            }
 | 
			
		||||
            if (layer_nr < m_highest_overhang_layer && layer_heights[layer_nr].second>0) {
 | 
			
		||||
                for (auto node_dist : all_layer_node_dist[layer_nr + 1])
 | 
			
		||||
                    layer_node_dist.emplace(node_dist + layer_heights[layer_nr].second);
 | 
			
		||||
            size_t layer_nr_next = layer_heights[layer_nr].next_layer_nr;
 | 
			
		||||
            if (layer_nr < m_highest_overhang_layer && layer_heights[layer_nr].height>0) {
 | 
			
		||||
                for (auto node_dist : all_layer_node_dist[layer_nr_next])
 | 
			
		||||
                    layer_node_dist.emplace(node_dist + layer_heights[layer_nr].height);
 | 
			
		||||
            }
 | 
			
		||||
            for (auto node_dist : layer_node_dist) {
 | 
			
		||||
                layer_radius.emplace(calc_branch_radius(branch_radius, node_dist, diameter_angle_scale_factor));
 | 
			
		||||
| 
						 | 
				
			
			@ -2577,14 +2601,12 @@ void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
 | 
			
		|||
        if (layer_contact_nodes.empty())
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
        int layer_nr_next = layer_nr - 1;
 | 
			
		||||
        while (layer_nr_next>=0 && layer_heights[layer_nr_next].second < EPSILON)
 | 
			
		||||
            layer_nr_next--;
 | 
			
		||||
        coordf_t print_z_next = layer_heights[layer_nr_next].first;
 | 
			
		||||
        coordf_t height_next = layer_heights[layer_nr_next].second;
 | 
			
		||||
        int      layer_nr_next = layer_heights[layer_nr].next_layer_nr;
 | 
			
		||||
        coordf_t print_z_next = layer_heights[layer_nr_next].print_z;
 | 
			
		||||
        coordf_t height_next = layer_heights[layer_nr_next].height;
 | 
			
		||||
 | 
			
		||||
        std::deque<std::pair<size_t, Node*>> unsupported_branch_leaves; // All nodes that are leaves on this layer that would result in unsupported ('mid-air') branches.
 | 
			
		||||
        const Layer* ts_layer = m_object->get_tree_support_layer(layer_nr);
 | 
			
		||||
        const Layer* ts_layer = m_object->get_support_layer(layer_nr);
 | 
			
		||||
 | 
			
		||||
        m_object->print()->set_status(60, (boost::format(_L("Support: propagate branches at layer %d")) % layer_nr).str());
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2620,7 +2642,7 @@ void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
 | 
			
		|||
 | 
			
		||||
            if (node.distance_to_top < 0) {
 | 
			
		||||
                // gap nodes do not merge or move
 | 
			
		||||
                Node* next_node = new Node(p_node->position, p_node->distance_to_top + 1, p_node->skin_direction, p_node->support_roof_layers_below - 1, p_node->to_buildplate, p_node,
 | 
			
		||||
                Node* next_node = new Node(p_node->position, p_node->distance_to_top + 1, layer_nr_next, p_node->support_roof_layers_below - 1, p_node->to_buildplate, p_node,
 | 
			
		||||
                    print_z_next, height_next);
 | 
			
		||||
                get_max_move_dist(next_node);
 | 
			
		||||
                next_node->is_merged = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -2755,8 +2777,8 @@ void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
 | 
			
		|||
                        parent = neighbour->parent;
 | 
			
		||||
 | 
			
		||||
                    const bool to_buildplate = !is_inside_ex(m_ts_data->get_avoidance(0, layer_nr_next), next_position);
 | 
			
		||||
                    Node *     next_node     = new Node(next_position, new_distance_to_top, node.skin_direction, new_support_roof_layers_below, to_buildplate, p_node,
 | 
			
		||||
                                               layer_heights[layer_nr_next].first, layer_heights[layer_nr_next].second, new_dist_mm_to_top);
 | 
			
		||||
                    Node *     next_node     = new Node(next_position, new_distance_to_top, layer_nr_next, new_support_roof_layers_below, to_buildplate, p_node,
 | 
			
		||||
                                               print_z_next, height_next, new_dist_mm_to_top);
 | 
			
		||||
                    next_node->movement = next_position - node.position;
 | 
			
		||||
                    get_max_move_dist(next_node);
 | 
			
		||||
                    next_node->is_merged     = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -2801,7 +2823,7 @@ void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
 | 
			
		|||
                if (node.type == ePolygon) {
 | 
			
		||||
                    // polygon node do not merge or move
 | 
			
		||||
                    const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[layer_nr], p_node->position);
 | 
			
		||||
                    Node *     next_node = new Node(p_node->position, p_node->distance_to_top + 1, p_node->skin_direction, p_node->support_roof_layers_below - 1, to_buildplate,
 | 
			
		||||
                    Node *     next_node = new Node(p_node->position, p_node->distance_to_top + 1, layer_nr_next, p_node->support_roof_layers_below - 1, to_buildplate,
 | 
			
		||||
                                               p_node, print_z_next, height_next);
 | 
			
		||||
                    next_node->max_move_dist = 0;
 | 
			
		||||
                    next_node->is_merged     = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -2868,13 +2890,13 @@ void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
 | 
			
		|||
 | 
			
		||||
                        if (is_line_cut_by_contour(node.position, neighbour)) continue;
 | 
			
		||||
 | 
			
		||||
                        if (is_slim)
 | 
			
		||||
                        if (/*is_slim*/1)
 | 
			
		||||
                            sum_direction += direction * (1 / dist2_to_neighbor);
 | 
			
		||||
                        else
 | 
			
		||||
                            sum_direction += direction;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (is_slim)
 | 
			
		||||
                    if (/*is_slim*/1)
 | 
			
		||||
                        move_to_neighbor_center = sum_direction;
 | 
			
		||||
                    else {
 | 
			
		||||
                        if (vsize2_with_unscale(sum_direction) <= max_move_distance2) {
 | 
			
		||||
| 
						 | 
				
			
			@ -2945,7 +2967,7 @@ void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
 | 
			
		|||
                }
 | 
			
		||||
 | 
			
		||||
                const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[layer_nr], next_layer_vertex);// !is_inside_ex(m_ts_data->get_avoidance(m_ts_data->m_xy_distance, layer_nr - 1), next_layer_vertex);
 | 
			
		||||
                Node *     next_node     = new Node(next_layer_vertex, node.distance_to_top + 1, node.skin_direction, node.support_roof_layers_below - 1, to_buildplate, p_node,
 | 
			
		||||
                Node *     next_node     = new Node(next_layer_vertex, node.distance_to_top + 1, layer_nr_next, node.support_roof_layers_below - 1, to_buildplate, p_node,
 | 
			
		||||
                    print_z_next, height_next);
 | 
			
		||||
                next_node->movement  = movement;
 | 
			
		||||
                get_max_move_dist(next_node);
 | 
			
		||||
| 
						 | 
				
			
			@ -2974,12 +2996,17 @@ void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
 | 
			
		|||
        {
 | 
			
		||||
            const auto& entry = unsupported_branch_leaves.back();
 | 
			
		||||
            Node* i_node = entry.second;
 | 
			
		||||
            for (size_t i_layer = entry.first; i_node != nullptr; ++i_layer, i_node = i_node->parent)
 | 
			
		||||
            for (; i_node != nullptr; i_node = i_node->parent)
 | 
			
		||||
            {
 | 
			
		||||
                size_t i_layer = i_node->obj_layer_nr;
 | 
			
		||||
                std::vector<Node*>::iterator to_erase = std::find(contact_nodes[i_layer].begin(), contact_nodes[i_layer].end(), i_node);
 | 
			
		||||
                if (to_erase != contact_nodes[i_layer].end())
 | 
			
		||||
                {
 | 
			
		||||
                    to_free_node_set.insert(*to_erase);
 | 
			
		||||
                    // update the parent-child chain
 | 
			
		||||
                    if(i_node->parent)
 | 
			
		||||
                        i_node->parent->child = i_node->child;
 | 
			
		||||
                    if(i_node->child)
 | 
			
		||||
                        i_node->child->parent = i_node->parent;
 | 
			
		||||
                    contact_nodes[i_layer].erase(to_erase);
 | 
			
		||||
                    to_free_node_set.insert(i_node);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3191,7 +3218,7 @@ void TreeSupport::adjust_layer_heights(std::vector<std::vector<Node*>>& contact_
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<std::pair<coordf_t, coordf_t>> TreeSupport::plan_layer_heights(std::vector<std::vector<Node*>>& contact_nodes)
 | 
			
		||||
std::vector<LayerHeightData> TreeSupport::plan_layer_heights(std::vector<std::vector<Node *>> &contact_nodes)
 | 
			
		||||
{
 | 
			
		||||
    const PrintObjectConfig& config = m_object->config();
 | 
			
		||||
    const PrintConfig &      print_config     = m_object->print()->config();
 | 
			
		||||
| 
						 | 
				
			
			@ -3205,21 +3232,19 @@ std::vector<std::pair<coordf_t, coordf_t>> TreeSupport::plan_layer_heights(std::
 | 
			
		|||
    }
 | 
			
		||||
    const size_t support_roof_layers = config.support_interface_top_layers.value;
 | 
			
		||||
    const int z_distance_top_layers = round_up_divide(scale_(z_distance_top), scale_(layer_height)) + 1;
 | 
			
		||||
    std::vector<std::pair<coordf_t, coordf_t>> layer_heights(contact_nodes.size(), std::pair<coordf_t, coordf_t>(0.0, 0.0));
 | 
			
		||||
    std::vector<LayerHeightData> layer_heights(contact_nodes.size());
 | 
			
		||||
    std::vector<int> bounds;
 | 
			
		||||
 | 
			
		||||
    if (!config.tree_support_adaptive_layer_height || layer_height == max_layer_height || !print_config.independent_support_layer_height) {
 | 
			
		||||
        for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) {
 | 
			
		||||
            layer_heights[layer_nr].first  = m_object->get_layer(layer_nr)->print_z;
 | 
			
		||||
            layer_heights[layer_nr].second = m_object->get_layer(layer_nr)->height;
 | 
			
		||||
            layer_heights[layer_nr] = {m_object->get_layer(layer_nr)->print_z, m_object->get_layer(layer_nr)->height, layer_nr > 0 ? size_t(layer_nr - 1) : 0};
 | 
			
		||||
        }
 | 
			
		||||
        return layer_heights;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bounds.push_back(0);
 | 
			
		||||
    // Keep first layer still
 | 
			
		||||
    layer_heights[0].first = m_object->get_layer(0)->print_z;
 | 
			
		||||
    layer_heights[0].second = m_object->get_layer(0)->height;
 | 
			
		||||
    layer_heights[0] = {m_object->get_layer(0)->print_z, m_object->get_layer(0)->height, 0};
 | 
			
		||||
    // Collect top contact layers
 | 
			
		||||
    for (int layer_nr = 1; layer_nr < contact_nodes.size(); layer_nr++)
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -3227,8 +3252,8 @@ std::vector<std::pair<coordf_t, coordf_t>> TreeSupport::plan_layer_heights(std::
 | 
			
		|||
            for (int i = 0; i < support_roof_layers + z_distance_top_layers + 1; i++) {
 | 
			
		||||
                if (layer_nr - i > 0) {
 | 
			
		||||
                    bounds.push_back(layer_nr - i);
 | 
			
		||||
                    layer_heights[layer_nr - i].first = m_object->get_layer(layer_nr - i)->print_z;
 | 
			
		||||
                    layer_heights[layer_nr - i].second = m_object->get_layer(layer_nr - i)->height;
 | 
			
		||||
                    layer_heights[layer_nr - i].print_z = m_object->get_layer(layer_nr - i)->print_z;
 | 
			
		||||
                    layer_heights[layer_nr - i].height = m_object->get_layer(layer_nr - i)->height;
 | 
			
		||||
                } 
 | 
			
		||||
                else {
 | 
			
		||||
                    break;
 | 
			
		||||
| 
						 | 
				
			
			@ -3259,19 +3284,29 @@ std::vector<std::pair<coordf_t, coordf_t>> TreeSupport::plan_layer_heights(std::
 | 
			
		|||
        for (int layer_nr = extr1_layer_nr + 1; layer_nr < extr2_layer_nr; layer_nr++) {
 | 
			
		||||
            // if (curr_layer_nodes.empty()) continue;
 | 
			
		||||
            if (std::abs(print_z - m_object->get_layer(layer_nr)->print_z) < step / 2 + EPSILON || extr_layers_left < 1) {
 | 
			
		||||
                layer_heights[layer_nr].first = print_z;
 | 
			
		||||
                layer_heights[layer_nr].second = step;
 | 
			
		||||
                layer_heights[layer_nr].print_z = print_z;
 | 
			
		||||
                layer_heights[layer_nr].height = step;
 | 
			
		||||
                print_z += step;
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                // can't clear curr_layer_nodes, or the model will have empty layers
 | 
			
		||||
                layer_heights[layer_nr].first = 0.0;
 | 
			
		||||
                layer_heights[layer_nr].second = 0.0;
 | 
			
		||||
                layer_heights[layer_nr].print_z = 0.0;
 | 
			
		||||
                layer_heights[layer_nr].height = 0.0;
 | 
			
		||||
                extr_layers_left--;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (int i = layer_heights.size() - 1; i >= 0; i--) {
 | 
			
		||||
        if (layer_heights[i].height < EPSILON) continue;
 | 
			
		||||
        for (int j = i - 1; j >= 0; j--) {
 | 
			
		||||
            if (layer_heights[j].height > EPSILON) {
 | 
			
		||||
                layer_heights[i].next_layer_nr = j;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
 | 
			
		||||
    // check bounds
 | 
			
		||||
    if (1)
 | 
			
		||||
| 
						 | 
				
			
			@ -3285,7 +3320,7 @@ std::vector<std::pair<coordf_t, coordf_t>> TreeSupport::plan_layer_heights(std::
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
#endif
 | 
			
		||||
    for (int i = 0; i < layer_heights.size(); i++) { BOOST_LOG_TRIVIAL(info) << "plan_layer_heights print_z, height: "<< layer_heights[i].first << "    " << layer_heights[i].second << std::endl; }
 | 
			
		||||
    for (int i = 0; i < layer_heights.size(); i++) { BOOST_LOG_TRIVIAL(info) << "plan_layer_heights print_z, height: "<< layer_heights[i].print_z << "    " << layer_heights[i].height << std::endl; }
 | 
			
		||||
 | 
			
		||||
    return layer_heights;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3299,12 +3334,11 @@ void TreeSupport::generate_contact_points(std::vector<std::vector<TreeSupport::N
 | 
			
		|||
    BoundingBox bounding_box = m_object->bounding_box();
 | 
			
		||||
    const Point bounding_box_size = bounding_box.max - bounding_box.min;
 | 
			
		||||
    constexpr double rotate_angle = 22.0 / 180.0 * M_PI;
 | 
			
		||||
    constexpr double thresh_big_overhang = SQ(scale_(10));
 | 
			
		||||
 | 
			
		||||
    const auto center = bounding_box_middle(bounding_box);
 | 
			
		||||
    const auto sin_angle = std::sin(rotate_angle);
 | 
			
		||||
    const auto cos_angle = std::cos(rotate_angle);
 | 
			
		||||
    const auto rotated_dims = Point(
 | 
			
		||||
    const Point rotated_dims = Point(
 | 
			
		||||
        bounding_box_size(0) * cos_angle + bounding_box_size(1) * sin_angle,
 | 
			
		||||
        bounding_box_size(0) * sin_angle + bounding_box_size(1) * cos_angle) / 2;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3346,7 +3380,7 @@ void TreeSupport::generate_contact_points(std::vector<std::vector<TreeSupport::N
 | 
			
		|||
    {
 | 
			
		||||
        if (m_object->print()->canceled())
 | 
			
		||||
            break;
 | 
			
		||||
        auto              ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
 | 
			
		||||
        auto              ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers);
 | 
			
		||||
        const ExPolygons &overhang = ts_layer->overhang_areas;
 | 
			
		||||
        auto &          curr_nodes = contact_nodes[layer_nr];
 | 
			
		||||
        if (overhang.empty())
 | 
			
		||||
| 
						 | 
				
			
			@ -3359,11 +3393,11 @@ void TreeSupport::generate_contact_points(std::vector<std::vector<TreeSupport::N
 | 
			
		|||
        for (const ExPolygon &overhang_part : overhang)
 | 
			
		||||
        {
 | 
			
		||||
            BoundingBox overhang_bounds = get_extents(overhang_part);
 | 
			
		||||
            if (config.support_style.value==smsTreeHybrid && overhang_part.area() > thresh_big_overhang) {
 | 
			
		||||
            if (config.support_style.value==smsTreeHybrid && overhang_part.area() > m_support_params.thresh_big_overhang) {
 | 
			
		||||
                Point candidate = overhang_bounds.center();
 | 
			
		||||
                if (!overhang_part.contains(candidate))
 | 
			
		||||
                    move_inside_expoly(overhang_part, candidate);
 | 
			
		||||
                Node *contact_node     = new Node(candidate, -z_distance_top_layers, (layer_nr) % 2, support_roof_layers + z_distance_top_layers, true, Node::NO_PARENT, print_z,
 | 
			
		||||
                Node *contact_node     = new Node(candidate, -z_distance_top_layers, layer_nr, support_roof_layers + z_distance_top_layers, true, Node::NO_PARENT, print_z,
 | 
			
		||||
                                              height, z_distance_top);
 | 
			
		||||
                contact_node->type = ePolygon;
 | 
			
		||||
                contact_node->overhang = &overhang_part;
 | 
			
		||||
| 
						 | 
				
			
			@ -3391,7 +3425,7 @@ void TreeSupport::generate_contact_points(std::vector<std::vector<TreeSupport::N
 | 
			
		|||
                        //if (!is_inside_ex(m_ts_data->get_collision(0, layer_nr), candidate))
 | 
			
		||||
                        {
 | 
			
		||||
                            constexpr bool to_buildplate = true;
 | 
			
		||||
                            Node *         contact_node  = new Node(candidate, -z_distance_top_layers, (layer_nr) % 2, support_roof_layers + z_distance_top_layers, to_buildplate,
 | 
			
		||||
                            Node *         contact_node  = new Node(candidate, -z_distance_top_layers, layer_nr, support_roof_layers + z_distance_top_layers, to_buildplate,
 | 
			
		||||
                                                          Node::NO_PARENT, print_z, height, z_distance_top);
 | 
			
		||||
                            contact_node->overhang = &overhang_part;
 | 
			
		||||
                            curr_nodes.emplace_back(contact_node);
 | 
			
		||||
| 
						 | 
				
			
			@ -3405,7 +3439,7 @@ void TreeSupport::generate_contact_points(std::vector<std::vector<TreeSupport::N
 | 
			
		|||
            {
 | 
			
		||||
                auto bbox = overhang_part.contour.bounding_box();
 | 
			
		||||
                Points candidates;
 | 
			
		||||
                if (ts_layer->overhang_types[&overhang_part] == TreeSupportLayer::Detected)
 | 
			
		||||
                if (ts_layer->overhang_types[&overhang_part] == SupportLayer::Detected)
 | 
			
		||||
                    candidates = {bbox.min, bounding_box_middle(bbox), bbox.max};
 | 
			
		||||
                else
 | 
			
		||||
                    candidates = {bounding_box_middle(bbox)};
 | 
			
		||||
| 
						 | 
				
			
			@ -3414,13 +3448,13 @@ void TreeSupport::generate_contact_points(std::vector<std::vector<TreeSupport::N
 | 
			
		|||
                    if (!overhang_part.contains(candidate))
 | 
			
		||||
                        move_inside_expoly(overhang_part, candidate);
 | 
			
		||||
                    constexpr bool   to_buildplate   = true;
 | 
			
		||||
                    Node *contact_node = new Node(candidate, -z_distance_top_layers, layer_nr % 2, support_roof_layers + z_distance_top_layers, to_buildplate, Node::NO_PARENT,
 | 
			
		||||
                    Node *contact_node = new Node(candidate, -z_distance_top_layers, layer_nr, support_roof_layers + z_distance_top_layers, to_buildplate, Node::NO_PARENT,
 | 
			
		||||
                                                  print_z, height, z_distance_top);
 | 
			
		||||
                    contact_node->overhang           = &overhang_part;
 | 
			
		||||
                    curr_nodes.emplace_back(contact_node);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (ts_layer->overhang_types[&overhang_part] == TreeSupportLayer::Detected) {
 | 
			
		||||
            if (ts_layer->overhang_types[&overhang_part] == SupportLayer::Detected) {
 | 
			
		||||
                // add points at corners
 | 
			
		||||
                auto &points = overhang_part.contour.points;
 | 
			
		||||
                int   nSize  = points.size();
 | 
			
		||||
| 
						 | 
				
			
			@ -3429,7 +3463,7 @@ void TreeSupport::generate_contact_points(std::vector<std::vector<TreeSupport::N
 | 
			
		|||
                    auto v1 = (pt - points[(i - 1 + nSize) % nSize]).cast<double>().normalized();
 | 
			
		||||
                    auto v2 = (pt - points[(i + 1) % nSize]).cast<double>().normalized();
 | 
			
		||||
                    if (v1.dot(v2) > -0.7) { // angle smaller than 135 degrees
 | 
			
		||||
                        Node *contact_node     = new Node(pt, -z_distance_top_layers, layer_nr % 2, support_roof_layers + z_distance_top_layers, true, Node::NO_PARENT, print_z,
 | 
			
		||||
                        Node *contact_node     = new Node(pt, -z_distance_top_layers, layer_nr, support_roof_layers + z_distance_top_layers, true, Node::NO_PARENT, print_z,
 | 
			
		||||
                                                      height, z_distance_top);
 | 
			
		||||
                        contact_node->overhang = &overhang_part;
 | 
			
		||||
                        contact_node->is_corner = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -3437,7 +3471,7 @@ void TreeSupport::generate_contact_points(std::vector<std::vector<TreeSupport::N
 | 
			
		|||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } 
 | 
			
		||||
            if(ts_layer->overhang_types[&overhang_part] == TreeSupportLayer::Enforced || is_slim){
 | 
			
		||||
            if(ts_layer->overhang_types[&overhang_part] == SupportLayer::Enforced || is_slim){
 | 
			
		||||
                // remove close points in Enforcers
 | 
			
		||||
                // auto above_nodes = contact_nodes[layer_nr - 1];
 | 
			
		||||
                if (!curr_nodes.empty() /*&& !above_nodes.empty()*/) {
 | 
			
		||||
| 
						 | 
				
			
			@ -3487,16 +3521,6 @@ void TreeSupport::generate_contact_points(std::vector<std::vector<TreeSupport::N
 | 
			
		|||
        
 | 
			
		||||
        BOOST_LOG_TRIVIAL(info) << "avg_node_per_layer=" << avg_node_per_layer << ", nodes_angle=" << nodes_angle;
 | 
			
		||||
    }
 | 
			
		||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
 | 
			
		||||
    std::ofstream contact_nodes_out;
 | 
			
		||||
    contact_nodes_out.open("./SVG/contact_nodes.txt");
 | 
			
		||||
    if (contact_nodes_out.is_open()) {
 | 
			
		||||
        for (int i = 0; i < contact_nodes.size(); i++) {
 | 
			
		||||
            if (!contact_nodes[i].empty())
 | 
			
		||||
                contact_nodes_out << i << std::endl;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
#endif // SUPPORT_TREE_DEBUG_TO_SVG
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TreeSupport::insert_dropped_node(std::vector<Node*>& nodes_layer, Node* p_node)
 | 
			
		||||
| 
						 | 
				
			
			@ -3621,13 +3645,18 @@ const ExPolygons& TreeSupportData::calculate_avoidance(const RadiusLayerPair& ke
 | 
			
		|||
        // if the layer at 2N below the current one but we won't exceed our limit unless there are N*N uncalculated layers
 | 
			
		||||
        // below our current one.
 | 
			
		||||
        constexpr auto max_recursion_depth = 100;
 | 
			
		||||
        size_t         layer_nr_next       = layer_nr;
 | 
			
		||||
        for (int i = 0; i < max_recursion_depth && layer_nr_next>0; i++) {
 | 
			
		||||
            layer_nr_next = layer_heights[layer_nr_next].next_layer_nr;
 | 
			
		||||
        }
 | 
			
		||||
        // Check if we would exceed the recursion limit by trying to process this layer
 | 
			
		||||
        if (layer_nr >= max_recursion_depth && m_avoidance_cache.find({radius, layer_nr - max_recursion_depth}) == m_avoidance_cache.end()) {
 | 
			
		||||
        if (layer_nr >= max_recursion_depth && m_avoidance_cache.find({radius, layer_nr_next}) == m_avoidance_cache.end()) {
 | 
			
		||||
            // Force the calculation of the layer `max_recursion_depth` below our current one, ignoring the result.
 | 
			
		||||
            get_avoidance(radius, layer_nr - max_recursion_depth);
 | 
			
		||||
            get_avoidance(radius, layer_nr_next);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ExPolygons        avoidance_areas = std::move(offset_ex(get_avoidance(radius, layer_nr - 1), scale_(-m_max_move)));
 | 
			
		||||
        layer_nr_next   = layer_heights[layer_nr].next_layer_nr;
 | 
			
		||||
        ExPolygons        avoidance_areas = std::move(offset_ex(get_avoidance(radius, layer_nr_next), scale_(-m_max_move)));
 | 
			
		||||
        const ExPolygons &collision       = get_collision(radius, layer_nr);
 | 
			
		||||
        avoidance_areas.insert(avoidance_areas.end(), collision.begin(), collision.end());
 | 
			
		||||
        avoidance_areas = std::move(union_ex(avoidance_areas));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,6 +21,15 @@ namespace Slic3r
 | 
			
		|||
class PrintObject;
 | 
			
		||||
class TreeSupport;
 | 
			
		||||
 | 
			
		||||
struct LayerHeightData
 | 
			
		||||
{
 | 
			
		||||
    coordf_t print_z       = 0;
 | 
			
		||||
    coordf_t height        = 0;
 | 
			
		||||
    size_t   next_layer_nr = 0;
 | 
			
		||||
    LayerHeightData()      = default;
 | 
			
		||||
    LayerHeightData(coordf_t z, coordf_t h, size_t next_layer) : print_z(z), height(h), next_layer_nr(next_layer) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \brief Lazily generates tree guidance volumes.
 | 
			
		||||
 *
 | 
			
		||||
| 
						 | 
				
			
			@ -79,6 +88,8 @@ public:
 | 
			
		|||
    Polygons get_contours(size_t layer_nr) const;
 | 
			
		||||
    Polygons get_contours_with_holes(size_t layer_nr) const;
 | 
			
		||||
 | 
			
		||||
    std::vector<LayerHeightData> layer_heights;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    /*!
 | 
			
		||||
     * \brief Convenience typedef for the keys to the caches
 | 
			
		||||
| 
						 | 
				
			
			@ -114,11 +125,6 @@ private:
 | 
			
		|||
     */
 | 
			
		||||
    const ExPolygons& calculate_avoidance(const RadiusLayerPair& key) const;
 | 
			
		||||
 | 
			
		||||
    /*!
 | 
			
		||||
     * \brief Polygons representing the limits of the printable area of the
 | 
			
		||||
     * machine
 | 
			
		||||
     */
 | 
			
		||||
    ExPolygon m_machine_border;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    bool is_slim = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -195,9 +201,9 @@ public:
 | 
			
		|||
     * \param storage The data storage where the mesh data is gotten from and
 | 
			
		||||
     * where the resulting support areas are stored.
 | 
			
		||||
     */
 | 
			
		||||
    void generate_support_areas();
 | 
			
		||||
    void generate();
 | 
			
		||||
 | 
			
		||||
    void detect_object_overhangs();
 | 
			
		||||
    void detect_overhangs();
 | 
			
		||||
 | 
			
		||||
    enum NodeType {
 | 
			
		||||
        eCircle,
 | 
			
		||||
| 
						 | 
				
			
			@ -215,7 +221,7 @@ public:
 | 
			
		|||
        Node()
 | 
			
		||||
         : distance_to_top(0)
 | 
			
		||||
         , position(Point(0, 0))
 | 
			
		||||
         , skin_direction(false)
 | 
			
		||||
         , obj_layer_nr(0)
 | 
			
		||||
         , support_roof_layers_below(0)
 | 
			
		||||
         , support_floor_layers_above(0)
 | 
			
		||||
         , to_buildplate(true)
 | 
			
		||||
| 
						 | 
				
			
			@ -224,11 +230,11 @@ public:
 | 
			
		|||
         , height(0.0)
 | 
			
		||||
        {}
 | 
			
		||||
 | 
			
		||||
        Node(const Point position, const int distance_to_top, const bool skin_direction, const int support_roof_layers_below, const bool to_buildplate, Node* parent,
 | 
			
		||||
        Node(const Point position, const int distance_to_top, const int obj_layer_nr, const int support_roof_layers_below, const bool to_buildplate, Node* parent,
 | 
			
		||||
             coordf_t     print_z_, coordf_t height_, coordf_t dist_mm_to_top_=0)
 | 
			
		||||
         : distance_to_top(distance_to_top)
 | 
			
		||||
         , position(position)
 | 
			
		||||
         , skin_direction(skin_direction)
 | 
			
		||||
         , obj_layer_nr(obj_layer_nr)
 | 
			
		||||
         , support_roof_layers_below(support_roof_layers_below)
 | 
			
		||||
         , support_floor_layers_above(0)
 | 
			
		||||
         , to_buildplate(to_buildplate)
 | 
			
		||||
| 
						 | 
				
			
			@ -292,6 +298,7 @@ public:
 | 
			
		|||
         */
 | 
			
		||||
        int support_roof_layers_below;
 | 
			
		||||
        int support_floor_layers_above;
 | 
			
		||||
        int obj_layer_nr;
 | 
			
		||||
 | 
			
		||||
        /*!
 | 
			
		||||
         * \brief Whether to try to go towards the build plate.
 | 
			
		||||
| 
						 | 
				
			
			@ -356,11 +363,15 @@ public:
 | 
			
		|||
        InfillPattern interface_fill_pattern;
 | 
			
		||||
        InfillPattern contact_fill_pattern;
 | 
			
		||||
        bool          with_sheath;
 | 
			
		||||
        const double thresh_big_overhang = SQ(scale_(10));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    int  avg_node_per_layer = 0;
 | 
			
		||||
    float nodes_angle       = 0;
 | 
			
		||||
    bool  has_overhangs = false;
 | 
			
		||||
    bool  has_sharp_tails = false;
 | 
			
		||||
    bool  has_cantilever = false;
 | 
			
		||||
    SupportType support_type;
 | 
			
		||||
 | 
			
		||||
    std::unique_ptr<FillLightning::Generator> generator;
 | 
			
		||||
    std::unordered_map<double, size_t> printZ_to_lightninglayer;
 | 
			
		||||
| 
						 | 
				
			
			@ -388,6 +399,12 @@ private:
 | 
			
		|||
    bool  with_infill                        = false;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /*!
 | 
			
		||||
     * \brief Polygons representing the limits of the printable area of the
 | 
			
		||||
     * machine
 | 
			
		||||
     */
 | 
			
		||||
    ExPolygon m_machine_border;
 | 
			
		||||
 | 
			
		||||
    /*!
 | 
			
		||||
     * \brief Draws circles around each node of the tree into the final support.
 | 
			
		||||
     *
 | 
			
		||||
| 
						 | 
				
			
			@ -423,7 +440,7 @@ private:
 | 
			
		|||
     * 
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    std::vector<std::pair<coordf_t, coordf_t>> plan_layer_heights(std::vector<std::vector<Node*>>& contact_nodes);
 | 
			
		||||
    std::vector<LayerHeightData> plan_layer_heights(std::vector<std::vector<Node *>> &contact_nodes);
 | 
			
		||||
    /*!
 | 
			
		||||
     * \brief Creates points where support contacts the model.
 | 
			
		||||
     *
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -753,9 +753,7 @@ inline std::pair<SlabLines, SlabLines> slice_slabs_make_lines(
 | 
			
		|||
    const std::vector<float>                        &zs,
 | 
			
		||||
    bool                                             top,
 | 
			
		||||
    bool                                             bottom,
 | 
			
		||||
    const ThrowOnCancel                              throw_on_cancel_fn,
 | 
			
		||||
    // BBS: solve conflicts (see declaration) and most elegant way I can get
 | 
			
		||||
    SlabSlicingConfig                                config)
 | 
			
		||||
    const ThrowOnCancel                              throw_on_cancel_fn)
 | 
			
		||||
{
 | 
			
		||||
    std::pair<SlabLines, SlabLines> out;
 | 
			
		||||
    SlabLines   &lines_top      = out.first;
 | 
			
		||||
| 
						 | 
				
			
			@ -774,7 +772,7 @@ inline std::pair<SlabLines, SlabLines> slice_slabs_make_lines(
 | 
			
		|||
 | 
			
		||||
    tbb::parallel_for(
 | 
			
		||||
        tbb::blocked_range<int>(0, int(indices.size())),
 | 
			
		||||
        [&vertices, &indices, &face_neighbors, &face_edge_ids, num_edges, &face_orientation, &zs, top, bottom, &lines_top, &lines_bottom, &lines_mutex_top, &lines_mutex_bottom, throw_on_cancel_fn, &config]
 | 
			
		||||
        [&vertices, &indices, &face_neighbors, &face_edge_ids, num_edges, &face_orientation, &zs, top, bottom, &lines_top, &lines_bottom, &lines_mutex_top, &lines_mutex_bottom, throw_on_cancel_fn]
 | 
			
		||||
        (const tbb::blocked_range<int> &range) {
 | 
			
		||||
            for (int face_idx = range.begin(); face_idx < range.end(); ++ face_idx) {
 | 
			
		||||
                if ((face_idx & 0x0ffff) == 0)
 | 
			
		||||
| 
						 | 
				
			
			@ -793,7 +791,7 @@ inline std::pair<SlabLines, SlabLines> slice_slabs_make_lines(
 | 
			
		|||
                    slice_facet_with_slabs<true>(vertices, indices, face_idx, neighbors, edge_ids, num_edges, zs, lines_top, lines_mutex_top);
 | 
			
		||||
                }
 | 
			
		||||
                // BBS: add vertical faces option
 | 
			
		||||
                if (bottom && (fo == FaceOrientation::Down || (config.isVertical && fo == FaceOrientation::Vertical) || fo == FaceOrientation::Degenerate)) {
 | 
			
		||||
                if (bottom && (fo == FaceOrientation::Down || fo == FaceOrientation::Degenerate)) {
 | 
			
		||||
                    Vec3i neighbors = face_neighbors[face_idx];
 | 
			
		||||
                    // Reset neighborship of this triangle in case the other triangle is oriented backwards from this one.
 | 
			
		||||
                    for (int i = 0; i < 3; ++ i)
 | 
			
		||||
| 
						 | 
				
			
			@ -1898,8 +1896,7 @@ void slice_mesh_slabs(
 | 
			
		|||
    const Transform3d                &trafo,
 | 
			
		||||
    std::vector<Polygons>            *out_top,
 | 
			
		||||
    std::vector<Polygons>            *out_bottom,
 | 
			
		||||
    std::function<void()>             throw_on_cancel,
 | 
			
		||||
    SlabSlicingConfig                config)
 | 
			
		||||
    std::function<void()>             throw_on_cancel)
 | 
			
		||||
{
 | 
			
		||||
    BOOST_LOG_TRIVIAL(debug) << "slice_mesh_slabs to polygons";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1978,7 +1975,7 @@ void slice_mesh_slabs(
 | 
			
		|||
    std::vector<Vec3i> face_edge_ids  = its_face_edge_ids(mesh, face_neighbors, true, &num_edges);
 | 
			
		||||
    std::pair<SlabLines, SlabLines> lines = slice_slabs_make_lines(
 | 
			
		||||
        vertices_transformed, mesh.indices, face_neighbors, face_edge_ids, num_edges, face_orientation, zs, 
 | 
			
		||||
        out_top != nullptr, out_bottom != nullptr, throw_on_cancel, config);
 | 
			
		||||
        out_top != nullptr, out_bottom != nullptr, throw_on_cancel);
 | 
			
		||||
 | 
			
		||||
    throw_on_cancel();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -46,19 +46,6 @@ struct MeshSlicingParamsEx : public MeshSlicingParams
 | 
			
		|||
    double        resolution { 0 };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// BBS: MusangKing - NEW: add paint-on support on vertical-faces
 | 
			
		||||
// this SlabSlicingConfig aiming to distinguish if slice_slabs_make_lines() outputs lines by slab_slicing on vertical faces
 | 
			
		||||
// e.g., for support enforcer operation: isVertical = true; for other color painting operations: isVertical = false (default).
 | 
			
		||||
// solve conflicts STUDIO-1183/970/1285
 | 
			
		||||
struct SlabSlicingConfig
 | 
			
		||||
{
 | 
			
		||||
    SlabSlicingConfig()
 | 
			
		||||
        : isVertical(false)
 | 
			
		||||
    {}
 | 
			
		||||
 | 
			
		||||
    bool isVertical;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// All the following slicing functions shall produce consistent results with the same mesh, same transformation matrix and slicing parameters.
 | 
			
		||||
// Namely, slice_mesh_slabs() shall produce consistent results with slice_mesh() and slice_mesh_ex() in the sense, that projections made by 
 | 
			
		||||
// slice_mesh_slabs() shall fall onto slicing planes produced by slice_mesh().
 | 
			
		||||
| 
						 | 
				
			
			@ -120,9 +107,7 @@ void slice_mesh_slabs(
 | 
			
		|||
    const Transform3d                &trafo,
 | 
			
		||||
    std::vector<Polygons>            *out_top,
 | 
			
		||||
    std::vector<Polygons>            *out_bottom,
 | 
			
		||||
    std::function<void()>             throw_on_cancel,
 | 
			
		||||
    // BBS: MusangKing
 | 
			
		||||
    SlabSlicingConfig                config = SlabSlicingConfig());
 | 
			
		||||
    std::function<void()>             throw_on_cancel);
 | 
			
		||||
 | 
			
		||||
// Project mesh upwards pointing surfaces / downwards pointing surfaces into 2D polygons.
 | 
			
		||||
void project_mesh(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,6 +34,7 @@
 | 
			
		|||
#define CLI_3MF_NOT_SUPPORT_MACHINE_CHANGE      -15
 | 
			
		||||
#define CLI_3MF_NEW_MACHINE_NOT_SUPPORTED       -16
 | 
			
		||||
#define CLI_PROCESS_NOT_COMPATIBLE     -17
 | 
			
		||||
#define CLI_INVALID_VALUES_IN_3MF      -18
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#define CLI_NO_SUITABLE_OBJECTS     -50
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@
 | 
			
		|||
#define SLIC3R_VERSION "@SLIC3R_VERSION@"
 | 
			
		||||
#define SoftFever_VERSION "@SoftFever_VERSION@"
 | 
			
		||||
#define SLIC3R_BUILD_ID "@SLIC3R_BUILD_ID@"
 | 
			
		||||
#define SLIC3R_BUILD_TIME "@SLIC3R_BUILD_TIME@"
 | 
			
		||||
//#define SLIC3R_RC_VERSION "@SLIC3R_VERSION@"
 | 
			
		||||
#define BBL_RELEASE_TO_PUBLIC @BBL_RELEASE_TO_PUBLIC@
 | 
			
		||||
#define BBL_INTERNAL_TESTING @BBL_INTERNAL_TESTING@
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,6 +28,8 @@ set(SLIC3R_GUI_SOURCES
 | 
			
		|||
    GUI/Widgets/SideMenuPopup.cpp
 | 
			
		||||
    GUI/Widgets/DropDown.cpp
 | 
			
		||||
    GUI/Widgets/DropDown.hpp
 | 
			
		||||
    GUI/Widgets/PopupWindow.cpp
 | 
			
		||||
    GUI/Widgets/PopupWindow.hpp
 | 
			
		||||
    GUI/Widgets/Label.cpp
 | 
			
		||||
    GUI/Widgets/Label.hpp
 | 
			
		||||
    GUI/Widgets/Scrollbar.cpp
 | 
			
		||||
| 
						 | 
				
			
			@ -205,6 +207,8 @@ set(SLIC3R_GUI_SOURCES
 | 
			
		|||
    GUI/GUI_ObjectTableSettings.hpp
 | 
			
		||||
    GUI/MeshUtils.cpp
 | 
			
		||||
    GUI/MeshUtils.hpp
 | 
			
		||||
    GUI/TickCode.cpp
 | 
			
		||||
    GUI/TickCode.hpp
 | 
			
		||||
    GUI/Tab.cpp
 | 
			
		||||
    GUI/Tab.hpp
 | 
			
		||||
    GUI/ParamsDialog.cpp
 | 
			
		||||
| 
						 | 
				
			
			@ -273,12 +277,14 @@ set(SLIC3R_GUI_SOURCES
 | 
			
		|||
    GUI/RemovableDriveManager.hpp
 | 
			
		||||
    GUI/SendSystemInfoDialog.cpp
 | 
			
		||||
    GUI/SendSystemInfoDialog.hpp
 | 
			
		||||
    GUI/SetBedTypeDialog.cpp
 | 
			
		||||
    GUI/SetBedTypeDialog.hpp
 | 
			
		||||
    GUI/PlateSettingsDialog.cpp
 | 
			
		||||
    GUI/PlateSettingsDialog.hpp
 | 
			
		||||
    GUI/ImGuiWrapper.hpp
 | 
			
		||||
    GUI/ImGuiWrapper.cpp
 | 
			
		||||
    GUI/DeviceManager.hpp
 | 
			
		||||
    GUI/DeviceManager.cpp
 | 
			
		||||
    GUI/HttpServer.hpp
 | 
			
		||||
    GUI/HttpServer.cpp
 | 
			
		||||
    Config/Snapshot.cpp
 | 
			
		||||
    Config/Snapshot.hpp
 | 
			
		||||
    Config/Version.cpp
 | 
			
		||||
| 
						 | 
				
			
			@ -364,6 +370,8 @@ set(SLIC3R_GUI_SOURCES
 | 
			
		|||
    GUI/PublishDialog.hpp
 | 
			
		||||
	GUI/RecenterDialog.cpp
 | 
			
		||||
    GUI/RecenterDialog.hpp
 | 
			
		||||
    GUI/PrivacyUpdateDialog.cpp
 | 
			
		||||
    GUI/PrivacyUpdateDialog.hpp
 | 
			
		||||
    GUI/BonjourDialog.cpp
 | 
			
		||||
    GUI/BonjourDialog.hpp
 | 
			
		||||
    GUI/BindDialog.cpp
 | 
			
		||||
| 
						 | 
				
			
			@ -476,6 +484,10 @@ endif ()
 | 
			
		|||
add_library(libslic3r_gui STATIC ${SLIC3R_GUI_SOURCES})
 | 
			
		||||
target_include_directories(libslic3r_gui PRIVATE Utils)
 | 
			
		||||
 | 
			
		||||
if (WIN32)
 | 
			
		||||
    target_include_directories(libslic3r_gui PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../deps/WebView2/include)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SLIC3R_GUI_SOURCES})
 | 
			
		||||
 | 
			
		||||
encoding_check(libslic3r_gui)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -390,6 +390,8 @@ std::array<float, 4> GLVolume::MODEL_NEGTIVE_COL    = {0.3f, 0.3f, 0.3f, 0.4f};
 | 
			
		|||
std::array<float, 4> GLVolume::SUPPORT_ENFORCER_COL = {0.3f, 0.3f, 1.0f, 0.4f};
 | 
			
		||||
std::array<float, 4> GLVolume::SUPPORT_BLOCKER_COL  = {1.0f, 0.3f, 0.3f, 0.4f};
 | 
			
		||||
 | 
			
		||||
std::array<float, 4> GLVolume::MODEL_HIDDEN_COL  = {0.f, 0.f, 0.f, 0.3f};
 | 
			
		||||
 | 
			
		||||
std::array<std::array<float, 4>, 5> GLVolume::MODEL_COLOR = { {
 | 
			
		||||
    { 1.0f, 1.0f, 0.0f, 1.f },
 | 
			
		||||
    { 1.0f, 0.5f, 0.5f, 1.f },
 | 
			
		||||
| 
						 | 
				
			
			@ -430,6 +432,7 @@ GLVolume::GLVolume(float r, float g, float b, float a)
 | 
			
		|||
    , selected(false)
 | 
			
		||||
    , disabled(false)
 | 
			
		||||
    , printable(true)
 | 
			
		||||
    , visible(true)
 | 
			
		||||
    , is_active(true)
 | 
			
		||||
    , zoom_to_volumes(true)
 | 
			
		||||
    , shader_outside_printer_detection_enabled(false)
 | 
			
		||||
| 
						 | 
				
			
			@ -521,6 +524,14 @@ void GLVolume::set_render_color()
 | 
			
		|||
        render_color[2] = UNPRINTABLE_COLOR[2];
 | 
			
		||||
        render_color[3] = UNPRINTABLE_COLOR[3];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //BBS set invisible color
 | 
			
		||||
    if (!visible) {
 | 
			
		||||
        render_color[0] = MODEL_HIDDEN_COL[0];
 | 
			
		||||
        render_color[1] = MODEL_HIDDEN_COL[1];
 | 
			
		||||
        render_color[2] = MODEL_HIDDEN_COL[2];
 | 
			
		||||
        render_color[3] = MODEL_HIDDEN_COL[3];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::array<float, 4> color_from_model_volume(const ModelVolume& model_volume)
 | 
			
		||||
| 
						 | 
				
			
			@ -684,6 +695,9 @@ void GLVolume::render(bool with_outline) const
 | 
			
		|||
                break;
 | 
			
		||||
 | 
			
		||||
            ModelObject* mo = model_objects[object_idx()];
 | 
			
		||||
            if (volume_idx() >= mo->volumes.size())
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            ModelVolume* mv = mo->volumes[volume_idx()];
 | 
			
		||||
            if (mv->mmu_segmentation_facets.empty())
 | 
			
		||||
                break;
 | 
			
		||||
| 
						 | 
				
			
			@ -1149,7 +1163,7 @@ int GLVolumeCollection::load_wipe_tower_preview(
 | 
			
		|||
    std::vector<std::array<float, 4>> extruder_colors = get_extruders_colors();
 | 
			
		||||
    std::vector<std::array<float, 4>> colors;
 | 
			
		||||
    GUI::PartPlateList& ppl = GUI::wxGetApp().plater()->get_partplate_list();
 | 
			
		||||
    std::vector<int> plate_extruders = ppl.get_plate(plate_idx)->get_extruders();
 | 
			
		||||
    std::vector<int> plate_extruders = ppl.get_plate(plate_idx)->get_extruders(true);
 | 
			
		||||
    TriangleMesh wipe_tower_shell = make_cube(width, depth, height);
 | 
			
		||||
    for (int extruder_id : plate_extruders) {
 | 
			
		||||
        if (extruder_id <= extruder_colors.size())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -269,6 +269,7 @@ public:
 | 
			
		|||
    static std::array<float, 4> MODEL_NEGTIVE_COL;
 | 
			
		||||
    static std::array<float, 4> SUPPORT_ENFORCER_COL;
 | 
			
		||||
    static std::array<float, 4> SUPPORT_BLOCKER_COL;
 | 
			
		||||
    static std::array<float, 4> MODEL_HIDDEN_COL;
 | 
			
		||||
 | 
			
		||||
    static void update_render_colors();
 | 
			
		||||
    static void load_render_colors();
 | 
			
		||||
| 
						 | 
				
			
			@ -286,6 +287,7 @@ public:
 | 
			
		|||
 | 
			
		||||
    GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f);
 | 
			
		||||
    GLVolume(const std::array<float, 4>& rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {}
 | 
			
		||||
    virtual ~GLVolume() = default;
 | 
			
		||||
 | 
			
		||||
    // BBS
 | 
			
		||||
protected:
 | 
			
		||||
| 
						 | 
				
			
			@ -363,6 +365,8 @@ public:
 | 
			
		|||
	    bool                disabled : 1;
 | 
			
		||||
	    // Is this object printable?
 | 
			
		||||
	    bool                printable : 1;
 | 
			
		||||
        // Is this object visible(in assemble view)?
 | 
			
		||||
	    bool                visible : 1;
 | 
			
		||||
	    // Whether or not this volume is active for rendering
 | 
			
		||||
	    bool                is_active : 1;
 | 
			
		||||
	    // Whether or not to use this volume when applying zoom_to_volumes()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -121,12 +121,13 @@ void AMSMaterialsSetting::create_panel_normal(wxWindow* parent)
 | 
			
		|||
 | 
			
		||||
    m_sizer_filament->Add(m_comboBox_filament, 1, wxALIGN_CENTER, 0);
 | 
			
		||||
 | 
			
		||||
    m_readonly_filament = new TextInput(parent, wxEmptyString, "", "", wxDefaultPosition, AMS_MATERIALS_SETTING_COMBOX_WIDTH, wxTE_READONLY);
 | 
			
		||||
    m_readonly_filament = new TextInput(parent, wxEmptyString, "", "", wxDefaultPosition, AMS_MATERIALS_SETTING_COMBOX_WIDTH, wxTE_READONLY | wxRIGHT);
 | 
			
		||||
    m_readonly_filament->SetBorderColor(StateColor(std::make_pair(0xDBDBDB, (int)StateColor::Focused), std::make_pair(0x009688, (int)StateColor::Hovered),
 | 
			
		||||
        std::make_pair(0xDBDBDB, (int)StateColor::Normal)));
 | 
			
		||||
    m_readonly_filament->GetTextCtrl()->Bind(wxEVT_SET_FOCUS, [](auto& e) {
 | 
			
		||||
        ;
 | 
			
		||||
        });
 | 
			
		||||
    m_readonly_filament->SetFont(::Label::Body_14);
 | 
			
		||||
    m_readonly_filament->SetLabelColor(AMS_MATERIALS_SETTING_GREY800);
 | 
			
		||||
    m_readonly_filament->GetTextCtrl()->Bind(wxEVT_SET_FOCUS, [](auto& e) {});
 | 
			
		||||
    m_readonly_filament->GetTextCtrl()->Hide();
 | 
			
		||||
    m_sizer_filament->Add(m_readonly_filament, 1, wxALIGN_CENTER, 0);
 | 
			
		||||
    m_readonly_filament->Hide();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -367,6 +368,13 @@ void AMSMaterialsSetting::enable_confirm_button(bool en)
 | 
			
		|||
    else {
 | 
			
		||||
        m_comboBox_filament->Show(en);
 | 
			
		||||
        m_readonly_filament->Show(!en);
 | 
			
		||||
 | 
			
		||||
        if ( !is_virtual_tray() ) {
 | 
			
		||||
            m_tip_readonly->SetLabelText(_L("Setting AMS slot information while printing is not supported"));
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            m_tip_readonly->SetLabelText(_L("Setting Virtual slot information while printing is not supported"));
 | 
			
		||||
        }
 | 
			
		||||
        m_tip_readonly->Show(!en);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -376,7 +384,7 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
 | 
			
		|||
    wxString k_text = m_input_k_val->GetTextCtrl()->GetValue();
 | 
			
		||||
    wxString n_text = m_input_n_val->GetTextCtrl()->GetValue();
 | 
			
		||||
 | 
			
		||||
    if (is_virtual_tray()) {
 | 
			
		||||
    if (is_virtual_tray() && obj && !obj->is_support_filament_edit_virtual_tray) {
 | 
			
		||||
        if (!ExtrusionCalibration::check_k_validation(k_text)) {
 | 
			
		||||
            wxString k_tips = _L("Please input a valid value (K in 0~0.5)");
 | 
			
		||||
            wxString kn_tips = _L("Please input a valid value (K in 0~0.5, N in 0.6~2.0)");
 | 
			
		||||
| 
						 | 
				
			
			@ -478,8 +486,12 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
 | 
			
		|||
                }
 | 
			
		||||
 | 
			
		||||
                // set filament
 | 
			
		||||
                if (!is_virtual_tray()) {
 | 
			
		||||
                    obj->command_ams_filament_settings(ams_id, tray_id, ams_filament_id, ams_setting_id, std::string(col_buf), m_filament_type, nozzle_temp_min_int, nozzle_temp_max_int);
 | 
			
		||||
                if (obj->is_support_filament_edit_virtual_tray || !is_virtual_tray()) {
 | 
			
		||||
                    if (is_virtual_tray()) {
 | 
			
		||||
                        obj->command_ams_filament_settings(255, VIRTUAL_TRAY_ID, ams_filament_id, ams_setting_id, std::string(col_buf), m_filament_type, nozzle_temp_min_int, nozzle_temp_max_int);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        obj->command_ams_filament_settings(ams_id, tray_id, ams_filament_id, ams_setting_id, std::string(col_buf), m_filament_type, nozzle_temp_min_int, nozzle_temp_max_int);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // set k / n value
 | 
			
		||||
| 
						 | 
				
			
			@ -548,7 +560,10 @@ void AMSMaterialsSetting::update_widgets()
 | 
			
		|||
{
 | 
			
		||||
    // virtual tray
 | 
			
		||||
    if (is_virtual_tray()) {
 | 
			
		||||
        m_panel_normal->Hide();
 | 
			
		||||
        if (obj && obj->is_support_filament_edit_virtual_tray)
 | 
			
		||||
            m_panel_normal->Show();
 | 
			
		||||
        else
 | 
			
		||||
            m_panel_normal->Hide();
 | 
			
		||||
        m_panel_kn->Show();
 | 
			
		||||
    } else if (obj && obj->is_function_supported(PrinterFunction::FUNC_EXTRUSION_CALI)) {
 | 
			
		||||
        m_panel_normal->Show();
 | 
			
		||||
| 
						 | 
				
			
			@ -583,7 +598,7 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
 | 
			
		|||
    m_input_k_val->GetTextCtrl()->SetValue(k);
 | 
			
		||||
    m_input_n_val->GetTextCtrl()->SetValue(n);
 | 
			
		||||
 | 
			
		||||
    if (is_virtual_tray()) {
 | 
			
		||||
    if (is_virtual_tray() && obj && !obj->is_support_filament_edit_virtual_tray) {
 | 
			
		||||
        m_button_confirm->Show();
 | 
			
		||||
        update();
 | 
			
		||||
        Layout();
 | 
			
		||||
| 
						 | 
				
			
			@ -609,7 +624,8 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
 | 
			
		|||
            m_panel_SN->Show();
 | 
			
		||||
            m_comboBox_filament->Hide();
 | 
			
		||||
            m_readonly_filament->Show();
 | 
			
		||||
            m_readonly_filament->GetTextCtrl()->SetLabel("Bambu " + filament);
 | 
			
		||||
            //m_readonly_filament->GetTextCtrl()->SetLabel("Bambu " + filament);
 | 
			
		||||
            m_readonly_filament->SetLabel("Bambu " + filament);
 | 
			
		||||
            m_input_nozzle_min->GetTextCtrl()->SetValue(temp_min);
 | 
			
		||||
            m_input_nozzle_max->GetTextCtrl()->SetValue(temp_max);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -712,6 +728,31 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
 | 
			
		|||
    if (preset_bundle) {
 | 
			
		||||
        for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); it++) {
 | 
			
		||||
            if (it->alias.compare(m_comboBox_filament->GetValue().ToStdString()) == 0) {
 | 
			
		||||
 | 
			
		||||
                //check is it in the filament blacklist
 | 
			
		||||
                bool in_blacklist = false;
 | 
			
		||||
                std::string action;
 | 
			
		||||
                std::string info;
 | 
			
		||||
                std::string filamnt_type;
 | 
			
		||||
                it->get_filament_type(filamnt_type);
 | 
			
		||||
 | 
			
		||||
                if (it->vendor) {
 | 
			
		||||
                    DeviceManager::check_filaments_in_blacklist(it->vendor->name, filamnt_type, in_blacklist, action, info);
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                if (in_blacklist) {
 | 
			
		||||
                    if (action == "prohibition") {
 | 
			
		||||
                        MessageDialog msg_wingow(nullptr, info, _L("Error"), wxICON_WARNING | wxOK);
 | 
			
		||||
                        msg_wingow.ShowModal();
 | 
			
		||||
                        m_comboBox_filament->SetSelection(m_filament_selection);
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (action == "warning") {
 | 
			
		||||
                        MessageDialog msg_wingow(nullptr, info, _L("Warning"), wxICON_INFORMATION | wxOK);
 | 
			
		||||
                        msg_wingow.ShowModal();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // ) if nozzle_temperature_range is found
 | 
			
		||||
                ConfigOption* opt_min = it->config.option("nozzle_temperature_range_low");
 | 
			
		||||
                if (opt_min) {
 | 
			
		||||
| 
						 | 
				
			
			@ -742,6 +783,8 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
 | 
			
		|||
                }
 | 
			
		||||
                if (!found_filament_type)
 | 
			
		||||
                    m_filament_type = "";
 | 
			
		||||
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -751,6 +794,8 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
 | 
			
		|||
    if (m_input_nozzle_max->GetTextCtrl()->GetValue().IsEmpty()) {
 | 
			
		||||
         m_input_nozzle_max->GetTextCtrl()->SetValue("220");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    m_filament_selection = evt.GetSelection();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMSMaterialsSetting::on_dpi_changed(const wxRect &suggested_rect) { this->Refresh(); }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -98,13 +98,14 @@ protected:
 | 
			
		|||
    TextInput*          m_input_k_val;
 | 
			
		||||
    wxStaticText*       m_n_param;
 | 
			
		||||
    TextInput*          m_input_n_val;
 | 
			
		||||
    int                 m_filament_selection;
 | 
			
		||||
 | 
			
		||||
#ifdef __APPLE__
 | 
			
		||||
    wxComboBox *m_comboBox_filament;
 | 
			
		||||
#else
 | 
			
		||||
    ComboBox *m_comboBox_filament;
 | 
			
		||||
#endif
 | 
			
		||||
    TextInput*          m_readonly_filament;
 | 
			
		||||
    TextInput*       m_readonly_filament;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}} // namespace Slic3r::GUI
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,9 +53,9 @@ void AMSSetting::create()
 | 
			
		|||
    m_sizer_Insert_material_tip->Add(0, 0, 0, wxLEFT, 10);
 | 
			
		||||
 | 
			
		||||
    // tip line1
 | 
			
		||||
    m_tip_Insert_material_line1 = new wxStaticText(m_panel_body, wxID_ANY,
 | 
			
		||||
                                                   _L("The AMS will automatically read the filament information when inserting a new Bambu Lab filament. This takes about 20 seconds."),
 | 
			
		||||
                                                   wxDefaultPosition, wxDefaultSize, 0);
 | 
			
		||||
    m_tip_Insert_material_line1 = new Label(m_panel_body,
 | 
			
		||||
        _L("The AMS will automatically read the filament information when inserting a new Bambu Lab filament. This takes about 20 seconds.")
 | 
			
		||||
    );
 | 
			
		||||
    m_tip_Insert_material_line1->SetFont(::Label::Body_13);
 | 
			
		||||
    m_tip_Insert_material_line1->SetForegroundColour(AMS_SETTING_GREY700);
 | 
			
		||||
    m_tip_Insert_material_line1->SetSize(wxSize(AMS_SETTING_BODY_WIDTH, -1));
 | 
			
		||||
| 
						 | 
				
			
			@ -64,9 +64,9 @@ void AMSSetting::create()
 | 
			
		|||
    m_sizer_Insert_material_tip_inline->Add(m_tip_Insert_material_line1, 0, wxEXPAND, 0);
 | 
			
		||||
 | 
			
		||||
    // tip line2
 | 
			
		||||
    m_tip_Insert_material_line2 = new wxStaticText(m_panel_body, wxID_ANY,
 | 
			
		||||
                                                   _L("Note: if new filament is inserted during  printing, the AMS will not automatically read any information until printing is completed."),
 | 
			
		||||
                                                   wxDefaultPosition, wxDefaultSize, 0);
 | 
			
		||||
    m_tip_Insert_material_line2 = new Label(m_panel_body,
 | 
			
		||||
        _L("Note: if new filament is inserted during  printing, the AMS will not automatically read any information until printing is completed.")
 | 
			
		||||
    );
 | 
			
		||||
    m_tip_Insert_material_line2->SetFont(::Label::Body_13);
 | 
			
		||||
    m_tip_Insert_material_line2->SetForegroundColour(AMS_SETTING_GREY700);
 | 
			
		||||
    m_tip_Insert_material_line2->SetSize(wxSize(AMS_SETTING_BODY_WIDTH, -1));
 | 
			
		||||
| 
						 | 
				
			
			@ -75,10 +75,9 @@ void AMSSetting::create()
 | 
			
		|||
    m_sizer_Insert_material_tip_inline->Add(m_tip_Insert_material_line2, 0, wxEXPAND | wxTOP, 8);
 | 
			
		||||
 | 
			
		||||
    // tip line2
 | 
			
		||||
    m_tip_Insert_material_line3 =
 | 
			
		||||
        new wxStaticText(m_panel_body, wxID_ANY,
 | 
			
		||||
                         _L("When inserting a new filament, the AMS will not automatically read its information, leaving it blank for you to enter manually."),
 | 
			
		||||
                         wxDefaultPosition, wxDefaultSize, 0);
 | 
			
		||||
    m_tip_Insert_material_line3 = new Label(m_panel_body,
 | 
			
		||||
        _L("When inserting a new filament, the AMS will not automatically read its information, leaving it blank for you to enter manually.")
 | 
			
		||||
    );
 | 
			
		||||
    m_tip_Insert_material_line3->SetFont(::Label::Body_13);
 | 
			
		||||
    m_tip_Insert_material_line3->SetForegroundColour(AMS_SETTING_GREY700);
 | 
			
		||||
    m_tip_Insert_material_line3->SetSize(wxSize(AMS_SETTING_BODY_WIDTH, -1));
 | 
			
		||||
| 
						 | 
				
			
			@ -109,18 +108,18 @@ void AMSSetting::create()
 | 
			
		|||
    // tip line
 | 
			
		||||
    m_sizer_starting_tip_inline = new wxBoxSizer(wxVERTICAL);
 | 
			
		||||
 | 
			
		||||
    m_tip_starting_line1 = new wxStaticText(m_panel_body, wxID_ANY,
 | 
			
		||||
                                            _L("The AMS will automatically read the information of inserted filament on start-up. It will take about 1 minute.The reading process will roll filament spools."),
 | 
			
		||||
                                            wxDefaultPosition, wxDefaultSize, 0);
 | 
			
		||||
    m_tip_starting_line1 = new Label(m_panel_body,
 | 
			
		||||
        _L("The AMS will automatically read the information of inserted filament on start-up. It will take about 1 minute.The reading process will roll filament spools.")
 | 
			
		||||
    );
 | 
			
		||||
    m_tip_starting_line1->SetFont(::Label::Body_13);
 | 
			
		||||
    m_tip_starting_line1->SetForegroundColour(AMS_SETTING_GREY700);
 | 
			
		||||
    m_tip_starting_line1->SetSize(wxSize(AMS_SETTING_BODY_WIDTH, -1));
 | 
			
		||||
    m_tip_starting_line1->Wrap(AMS_SETTING_BODY_WIDTH);
 | 
			
		||||
    m_sizer_starting_tip_inline->Add(m_tip_starting_line1, 0, wxEXPAND, 0);
 | 
			
		||||
 | 
			
		||||
    m_tip_starting_line2 = new wxStaticText(m_panel_body, wxID_ANY,
 | 
			
		||||
                                            _L("The AMS will not automatically read information from inserted filament during startup and will continue to use the information recorded before the last shutdown."),
 | 
			
		||||
                                            wxDefaultPosition, wxDefaultSize, 0);
 | 
			
		||||
    m_tip_starting_line2 = new Label(m_panel_body,
 | 
			
		||||
        _L("The AMS will not automatically read information from inserted filament during startup and will continue to use the information recorded before the last shutdown.")
 | 
			
		||||
    );
 | 
			
		||||
    m_tip_starting_line2->SetFont(::Label::Body_13);
 | 
			
		||||
    m_tip_starting_line2->SetForegroundColour(AMS_SETTING_GREY700);
 | 
			
		||||
    m_tip_starting_line2->SetSize(wxSize(AMS_SETTING_BODY_WIDTH, -1));
 | 
			
		||||
| 
						 | 
				
			
			@ -148,9 +147,9 @@ void AMSSetting::create()
 | 
			
		|||
    // tip line
 | 
			
		||||
    m_sizer_remain_inline = new wxBoxSizer(wxVERTICAL);
 | 
			
		||||
 | 
			
		||||
    m_tip_remain_line1 = new wxStaticText(m_panel_body, wxID_ANY,
 | 
			
		||||
        _L("The AMS will estimate Bambu filament's remaining capacity after the filament info is updated. During printing, remaining capacity will be updated automatically."),
 | 
			
		||||
        wxDefaultPosition, wxDefaultSize, 0);
 | 
			
		||||
    m_tip_remain_line1 = new Label(m_panel_body,
 | 
			
		||||
        _L("The AMS will estimate Bambu filament's remaining capacity after the filament info is updated. During printing, remaining capacity will be updated automatically.")
 | 
			
		||||
    );
 | 
			
		||||
    m_tip_remain_line1->SetFont(::Label::Body_13);
 | 
			
		||||
    m_tip_remain_line1->SetForegroundColour(AMS_SETTING_GREY700);
 | 
			
		||||
    m_tip_remain_line1->SetSize(wxSize(AMS_SETTING_BODY_WIDTH, -1));
 | 
			
		||||
| 
						 | 
				
			
			@ -178,9 +177,9 @@ void AMSSetting::create()
 | 
			
		|||
    // tip line
 | 
			
		||||
    m_sizer_switch_filament_inline = new wxBoxSizer(wxVERTICAL);
 | 
			
		||||
 | 
			
		||||
    m_tip_switch_filament_line1 = new wxStaticText(m_panel_body, wxID_ANY,
 | 
			
		||||
        _L("AMS will continue to another spool with the same properties of filament automatically when current filament runs out"),
 | 
			
		||||
        wxDefaultPosition, wxDefaultSize, 0);
 | 
			
		||||
    m_tip_switch_filament_line1 = new Label(m_panel_body,
 | 
			
		||||
        _L("AMS will continue to another spool with the same properties of filament automatically when current filament runs out")
 | 
			
		||||
    );
 | 
			
		||||
    m_tip_switch_filament_line1->SetFont(::Label::Body_13);
 | 
			
		||||
    m_tip_switch_filament_line1->SetForegroundColour(AMS_SETTING_GREY700);
 | 
			
		||||
    m_tip_switch_filament_line1->SetSize(wxSize(AMS_SETTING_BODY_WIDTH, -1));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -49,22 +49,22 @@ protected:
 | 
			
		|||
    wxPanel *     m_panel_body;
 | 
			
		||||
    CheckBox *    m_checkbox_Insert_material_auto_read;
 | 
			
		||||
    wxStaticText *m_title_Insert_material_auto_read;
 | 
			
		||||
    wxStaticText *m_tip_Insert_material_line1;
 | 
			
		||||
    wxStaticText *m_tip_Insert_material_line2;
 | 
			
		||||
    wxStaticText *m_tip_Insert_material_line3;
 | 
			
		||||
    Label* m_tip_Insert_material_line1;
 | 
			
		||||
    Label* m_tip_Insert_material_line2;
 | 
			
		||||
    Label* m_tip_Insert_material_line3;
 | 
			
		||||
 | 
			
		||||
    CheckBox *    m_checkbox_starting_auto_read;
 | 
			
		||||
    wxStaticText *m_title_starting_auto_read;
 | 
			
		||||
    wxStaticText *m_tip_starting_line1;
 | 
			
		||||
    wxStaticText *m_tip_starting_line2;
 | 
			
		||||
    Label* m_tip_starting_line1;
 | 
			
		||||
    Label* m_tip_starting_line2;
 | 
			
		||||
 | 
			
		||||
    CheckBox *    m_checkbox_remain;
 | 
			
		||||
    wxStaticText *m_title_remain;
 | 
			
		||||
    wxStaticText *m_tip_remain_line1;
 | 
			
		||||
    Label* m_tip_remain_line1;
 | 
			
		||||
 | 
			
		||||
    CheckBox* m_checkbox_switch_filament;
 | 
			
		||||
    wxStaticText* m_title_switch_filament;
 | 
			
		||||
    wxStaticText* m_tip_switch_filament_line1;
 | 
			
		||||
    Label* m_tip_switch_filament_line1;
 | 
			
		||||
 | 
			
		||||
    wxStaticText *m_tip_ams_img;
 | 
			
		||||
    Button *     m_button_auto_demarcate;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -257,6 +257,13 @@ AboutDialog::AboutDialog()
 | 
			
		|||
            version->SetBackgroundColour(wxColour("#009688"));
 | 
			
		||||
 | 
			
		||||
        vesizer->Add(version, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, FromDIP(5));
 | 
			
		||||
#if BBL_INTERNAL_TESTING
 | 
			
		||||
        wxString build_time = wxString::Format("Build Time: %s", std::string(SLIC3R_BUILD_TIME));
 | 
			
		||||
        wxStaticText* build_time_text = new wxStaticText(this, wxID_ANY, build_time, wxDefaultPosition, wxDefaultSize);
 | 
			
		||||
        build_time_text->SetForegroundColour(wxColour("#FFFFFE"));
 | 
			
		||||
        build_time_text->SetBackgroundColour(wxColour("#00AF42"));
 | 
			
		||||
        vesizer->Add(build_time_text, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, FromDIP(5));
 | 
			
		||||
#endif
 | 
			
		||||
        vesizer->Add(0, 0, 1, wxEXPAND, FromDIP(5));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -207,11 +207,11 @@ void MaterialItem::doRender(wxDC &dc)
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
 AmsMapingPopup::AmsMapingPopup(wxWindow *parent) 
 | 
			
		||||
    :wxPopupTransientWindow(parent, wxBORDER_NONE)
 | 
			
		||||
    : PopupWindow(parent, wxBORDER_NONE)
 | 
			
		||||
 {
 | 
			
		||||
     SetSize(wxSize(FromDIP(300), -1));
 | 
			
		||||
     SetMinSize(wxSize(FromDIP(300), -1));
 | 
			
		||||
     SetMaxSize(wxSize(FromDIP(300), -1));
 | 
			
		||||
     SetSize(wxSize(FromDIP(252), -1));
 | 
			
		||||
     SetMinSize(wxSize(FromDIP(252), -1));
 | 
			
		||||
     SetMaxSize(wxSize(FromDIP(252), -1));
 | 
			
		||||
     Bind(wxEVT_PAINT, &AmsMapingPopup::paintEvent, this);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -243,21 +243,36 @@ void MaterialItem::doRender(wxDC &dc)
 | 
			
		|||
     title_panel->Fit();
 | 
			
		||||
 | 
			
		||||
     m_sizer_list = new wxBoxSizer(wxVERTICAL);
 | 
			
		||||
     for (auto i = 0; i < AMS_TOTAL_COUNT; i++) {
 | 
			
		||||
         auto sizer_mapping_list = new wxBoxSizer(wxHORIZONTAL);
 | 
			
		||||
         /*auto ams_mapping_item_container = new wxStaticBitmap(this, wxID_ANY, create_scaled_bitmap("ams_mapping_container", this, 78), wxDefaultPosition,
 | 
			
		||||
             wxSize(FromDIP(230), FromDIP(78)), 0);*/
 | 
			
		||||
         auto ams_mapping_item_container = new MappingContainer(this);
 | 
			
		||||
         ams_mapping_item_container->SetSizer(sizer_mapping_list);
 | 
			
		||||
         ams_mapping_item_container->Layout();
 | 
			
		||||
         //ams_mapping_item_container->Hide();
 | 
			
		||||
         m_amsmapping_container_sizer_list.push_back(sizer_mapping_list);
 | 
			
		||||
         m_amsmapping_container_list.push_back(ams_mapping_item_container);
 | 
			
		||||
         m_sizer_list->Add(ams_mapping_item_container, 0, wxALIGN_CENTER_HORIZONTAL|wxTOP|wxBOTTOM, FromDIP(5));
 | 
			
		||||
     }
 | 
			
		||||
 | 
			
		||||
     m_warning_text = new wxStaticText(this, wxID_ANY, wxEmptyString);
 | 
			
		||||
     m_warning_text->SetForegroundColour(wxColour(0xFF, 0x6F, 0x00));
 | 
			
		||||
     m_warning_text->SetFont(::Label::Body_12);
 | 
			
		||||
     auto cant_not_match_tip = _L("Note: Only the AMS slots loaded with the same material type can be selected.");
 | 
			
		||||
     m_warning_text->SetLabel(format_text(cant_not_match_tip));
 | 
			
		||||
     m_warning_text->SetMinSize(wxSize(FromDIP(280), FromDIP(-1)));
 | 
			
		||||
     m_warning_text->Wrap(FromDIP(280));
 | 
			
		||||
     m_warning_text->SetMinSize(wxSize(FromDIP(248), FromDIP(-1)));
 | 
			
		||||
     m_warning_text->Wrap(FromDIP(248));
 | 
			
		||||
 | 
			
		||||
     m_sizer_main->Add(title_panel, 0, wxEXPAND | wxALL, FromDIP(2));
 | 
			
		||||
     m_sizer_main->Add(0, 0, 0, wxTOP, FromDIP(5));
 | 
			
		||||
     m_sizer_main->Add(m_sizer_list, 0, wxEXPAND | wxALL, FromDIP(0));
 | 
			
		||||
     m_sizer_main->Add(m_warning_text, 0, wxEXPAND | wxALL, FromDIP(10));
 | 
			
		||||
     m_sizer_main->Add(0, 0, 0, wxTOP, FromDIP(5));
 | 
			
		||||
     m_sizer_main->Add(m_warning_text, 0, wxEXPAND | wxALL, FromDIP(6));
 | 
			
		||||
 | 
			
		||||
     SetSizer(m_sizer_main);
 | 
			
		||||
     Layout();
 | 
			
		||||
     Fit();
 | 
			
		||||
 }
 | 
			
		||||
 | 
			
		||||
 wxString AmsMapingPopup::format_text(wxString &m_msg)
 | 
			
		||||
| 
						 | 
				
			
			@ -315,15 +330,29 @@ void AmsMapingPopup::on_left_down(wxMouseEvent &evt)
 | 
			
		|||
void AmsMapingPopup::update_ams_data(std::map<std::string, Ams*> amsList) 
 | 
			
		||||
{ 
 | 
			
		||||
    m_has_unmatch_filament = false;
 | 
			
		||||
    m_mapping_item_list.clear();
 | 
			
		||||
    if (m_amsmapping_sizer_list.size() > 0) {
 | 
			
		||||
        for (wxBoxSizer *bz : m_amsmapping_sizer_list) { bz->Clear(true); }
 | 
			
		||||
        m_amsmapping_sizer_list.clear();
 | 
			
		||||
    //m_mapping_item_list.clear();
 | 
			
		||||
 | 
			
		||||
    for (auto& ams_container : m_amsmapping_container_list) {
 | 
			
		||||
        ams_container->Hide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    for (wxWindow *mitem : m_mapping_item_list) {
 | 
			
		||||
        mitem->Destroy();
 | 
			
		||||
        mitem = nullptr;
 | 
			
		||||
    }
 | 
			
		||||
     m_mapping_item_list.clear();
 | 
			
		||||
 | 
			
		||||
    if (m_amsmapping_container_sizer_list.size() > 0) {
 | 
			
		||||
        for (wxBoxSizer *siz : m_amsmapping_container_sizer_list) { 
 | 
			
		||||
            siz->Clear(true); 
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
   
 | 
			
		||||
    std::map<std::string, Ams *>::iterator ams_iter;
 | 
			
		||||
 | 
			
		||||
    BOOST_LOG_TRIVIAL(trace) << "ams_mapping total count " << amsList.size();
 | 
			
		||||
    int m_amsmapping_container_list_index = 0;
 | 
			
		||||
 | 
			
		||||
    for (ams_iter = amsList.begin(); ams_iter != amsList.end(); ams_iter++) {
 | 
			
		||||
        
 | 
			
		||||
| 
						 | 
				
			
			@ -355,7 +384,10 @@ void AmsMapingPopup::update_ams_data(std::map<std::string, Ams*> amsList)
 | 
			
		|||
 | 
			
		||||
            tray_datas.push_back(td);
 | 
			
		||||
        }
 | 
			
		||||
        add_ams_mapping(tray_datas);
 | 
			
		||||
 | 
			
		||||
        m_amsmapping_container_list[m_amsmapping_container_list_index]->Show();
 | 
			
		||||
        add_ams_mapping(tray_datas, m_amsmapping_container_list[m_amsmapping_container_list_index], m_amsmapping_container_sizer_list[m_amsmapping_container_list_index]);
 | 
			
		||||
        m_amsmapping_container_list_index++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -406,40 +438,38 @@ std::vector<TrayData> AmsMapingPopup::parse_ams_mapping(std::map<std::string, Am
 | 
			
		|||
    return m_tray_data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AmsMapingPopup::add_ams_mapping(std::vector<TrayData> tray_data)
 | 
			
		||||
void AmsMapingPopup::add_ams_mapping(std::vector<TrayData> tray_data, wxWindow* container, wxBoxSizer* sizer)
 | 
			
		||||
{ 
 | 
			
		||||
    auto sizer_mapping_list = new wxBoxSizer(wxHORIZONTAL);
 | 
			
		||||
 | 
			
		||||
    sizer->Add(0,0,0,wxLEFT,FromDIP(6));
 | 
			
		||||
    for (auto i = 0; i < tray_data.size(); i++) {
 | 
			
		||||
        wxBoxSizer *sizer_mapping_item   = new wxBoxSizer(wxVERTICAL);
 | 
			
		||||
 | 
			
		||||
        // set number
 | 
			
		||||
        auto number = new wxStaticText(this, wxID_ANY, wxGetApp().transition_tridid(tray_data[i].id), wxDefaultPosition, wxDefaultSize, 0);
 | 
			
		||||
       /* auto number = new wxStaticText(this, wxID_ANY, wxGetApp().transition_tridid(tray_data[i].id), wxDefaultPosition, wxDefaultSize, 0);
 | 
			
		||||
        number->SetFont(::Label::Body_13);
 | 
			
		||||
        number->SetForegroundColour(wxColour(0X6B, 0X6B, 0X6B));
 | 
			
		||||
        number->Wrap(-1);
 | 
			
		||||
        number->Wrap(-1);*/
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // set button
 | 
			
		||||
        MappingItem *m_filament_name = new MappingItem(this);
 | 
			
		||||
        m_filament_name->SetSize(wxSize(FromDIP(62), FromDIP(22)));
 | 
			
		||||
        m_filament_name->SetMinSize(wxSize(FromDIP(62), FromDIP(22)));
 | 
			
		||||
        m_filament_name->SetMaxSize(wxSize(FromDIP(62), FromDIP(22)));
 | 
			
		||||
        //m_filament_name->SetCornerRadius(5);
 | 
			
		||||
        m_filament_name->SetFont(::Label::Body_12);
 | 
			
		||||
        m_mapping_item_list.push_back(m_filament_name);
 | 
			
		||||
      
 | 
			
		||||
        MappingItem *m_mapping_item = new MappingItem(container);
 | 
			
		||||
        m_mapping_item->SetSize(wxSize(FromDIP(68 * 0.7), FromDIP(100 * 0.6)));
 | 
			
		||||
        m_mapping_item->SetMinSize(wxSize(FromDIP(68 * 0.7), FromDIP(100 * 0.6)));
 | 
			
		||||
        m_mapping_item->SetMaxSize(wxSize(FromDIP(68 * 0.7), FromDIP(100 * 0.6)));
 | 
			
		||||
        //m_mapping_item->SetCornerRadius(5);
 | 
			
		||||
        m_mapping_item->SetFont(::Label::Body_12);
 | 
			
		||||
        m_mapping_item_list.push_back(m_mapping_item);
 | 
			
		||||
 | 
			
		||||
        if (tray_data[i].type == NORMAL) {
 | 
			
		||||
            if (is_match_material(tray_data[i].filament_type)) { 
 | 
			
		||||
                m_filament_name->set_data(tray_data[i].colour, tray_data[i].name, tray_data[i]);
 | 
			
		||||
                m_mapping_item->set_data(tray_data[i].colour, tray_data[i].name, tray_data[i]);
 | 
			
		||||
            } else {
 | 
			
		||||
                m_filament_name->set_data(wxColour(0xEE,0xEE,0xEE), tray_data[i].name, tray_data[i], true);
 | 
			
		||||
                m_mapping_item->set_data(wxColour(0xEE,0xEE,0xEE), tray_data[i].name, tray_data[i], true);
 | 
			
		||||
                m_has_unmatch_filament = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            m_filament_name->Bind(wxEVT_LEFT_DOWN, [this, tray_data, i, m_filament_name](wxMouseEvent &e) {
 | 
			
		||||
            m_mapping_item->Bind(wxEVT_LEFT_DOWN, [this, tray_data, i, m_mapping_item](wxMouseEvent &e) {
 | 
			
		||||
                if (!is_match_material(tray_data[i].filament_type)) return;
 | 
			
		||||
                m_filament_name->send_event(m_current_filament_id);
 | 
			
		||||
                m_mapping_item->send_event(m_current_filament_id);
 | 
			
		||||
                Dismiss();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -447,29 +477,30 @@ void AmsMapingPopup::add_ams_mapping(std::vector<TrayData> tray_data)
 | 
			
		|||
 | 
			
		||||
        // temp
 | 
			
		||||
        if (tray_data[i].type == EMPTY) {
 | 
			
		||||
            m_filament_name->set_data(wxColour(0xCE, 0xCE, 0xCE), "-", tray_data[i]);
 | 
			
		||||
            m_filament_name->Bind(wxEVT_LEFT_DOWN, [this, tray_data, i, m_filament_name](wxMouseEvent &e) {
 | 
			
		||||
                m_filament_name->send_event(m_current_filament_id);
 | 
			
		||||
            m_mapping_item->set_data(wxColour(0xCE, 0xCE, 0xCE), "-", tray_data[i]);
 | 
			
		||||
            m_mapping_item->Bind(wxEVT_LEFT_DOWN, [this, tray_data, i, m_mapping_item](wxMouseEvent &e) {
 | 
			
		||||
                m_mapping_item->send_event(m_current_filament_id);
 | 
			
		||||
                Dismiss();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // third party
 | 
			
		||||
        if (tray_data[i].type == THIRD) {
 | 
			
		||||
            m_filament_name->set_data(wxColour(0xCE, 0xCE, 0xCE), "?", tray_data[i]);
 | 
			
		||||
            m_filament_name->Bind(wxEVT_LEFT_DOWN, [this, tray_data, i, m_filament_name](wxMouseEvent &e) {
 | 
			
		||||
                m_filament_name->send_event(m_current_filament_id);
 | 
			
		||||
            m_mapping_item->set_data(wxColour(0xCE, 0xCE, 0xCE), "?", tray_data[i]);
 | 
			
		||||
            m_mapping_item->Bind(wxEVT_LEFT_DOWN, [this, tray_data, i, m_mapping_item](wxMouseEvent &e) {
 | 
			
		||||
                m_mapping_item->send_event(m_current_filament_id);
 | 
			
		||||
                Dismiss();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        sizer_mapping_item->Add(number, 0, wxALIGN_CENTER_HORIZONTAL, 0);
 | 
			
		||||
        sizer_mapping_item->Add(m_filament_name, 0, wxALIGN_CENTER_HORIZONTAL, 0);
 | 
			
		||||
        sizer_mapping_list->Add(sizer_mapping_item, 0, wxALL, FromDIP(5));
 | 
			
		||||
        m_amsmapping_sizer_list.push_back(sizer_mapping_list);
 | 
			
		||||
        //sizer_mapping_item->Add(number, 0, wxALIGN_CENTER_HORIZONTAL, 0);
 | 
			
		||||
        //sizer_mapping_item->Add(m_mapping_item, 0, wxALIGN_CENTER_HORIZONTAL, 0);
 | 
			
		||||
        m_mapping_item->set_tray_index(wxGetApp().transition_tridid(tray_data[i].id));
 | 
			
		||||
        sizer->Add(0,0,0,wxRIGHT,FromDIP(6));
 | 
			
		||||
        sizer->Add(m_mapping_item, 0, wxTOP, FromDIP(1));
 | 
			
		||||
    }
 | 
			
		||||
    m_sizer_list->Add(sizer_mapping_list, 0, wxALIGN_CENTER_HORIZONTAL, 0);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AmsMapingPopup::OnDismiss()
 | 
			
		||||
| 
						 | 
				
			
			@ -479,7 +510,7 @@ void AmsMapingPopup::OnDismiss()
 | 
			
		|||
 | 
			
		||||
bool AmsMapingPopup::ProcessLeftDown(wxMouseEvent &event) 
 | 
			
		||||
{
 | 
			
		||||
    return wxPopupTransientWindow::ProcessLeftDown(event);
 | 
			
		||||
    return PopupWindow::ProcessLeftDown(event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AmsMapingPopup::paintEvent(wxPaintEvent &evt) 
 | 
			
		||||
| 
						 | 
				
			
			@ -514,7 +545,7 @@ void MappingItem::send_event(int fliament_id)
 | 
			
		|||
    wxString param = wxString::Format("%d|%d|%d|%s|%d", m_coloul.Red(), m_coloul.Green(), m_coloul.Blue(), number, fliament_id);
 | 
			
		||||
    event.SetString(param);
 | 
			
		||||
    event.SetEventObject(this->GetParent()->GetParent());
 | 
			
		||||
    wxPostEvent(this->GetParent()->GetParent(), event);
 | 
			
		||||
    wxPostEvent(this->GetParent()->GetParent()->GetParent(), event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 void MappingItem::msw_rescale() 
 | 
			
		||||
| 
						 | 
				
			
			@ -551,7 +582,7 @@ void MappingItem::render(wxDC &dc)
 | 
			
		|||
#endif
 | 
			
		||||
 | 
			
		||||
    // materials name
 | 
			
		||||
    dc.SetFont(::Label::Body_12);
 | 
			
		||||
    dc.SetFont(::Label::Head_13);
 | 
			
		||||
 | 
			
		||||
    auto txt_colour = m_coloul.GetLuminance() < 0.5 ? *wxWHITE : wxColour(0x26, 0x2E, 0x30);
 | 
			
		||||
    txt_colour      = m_unmatch ? wxColour(0xCE, 0xCE, 0xCE) : txt_colour;
 | 
			
		||||
| 
						 | 
				
			
			@ -563,8 +594,15 @@ void MappingItem::render(wxDC &dc)
 | 
			
		|||
        m_name = m_name.substr(0, 3) + "." + m_name.substr(m_name.length() - 1);
 | 
			
		||||
    }*/
 | 
			
		||||
 | 
			
		||||
    auto txt_size = dc.GetTextExtent(m_name);
 | 
			
		||||
    dc.DrawText(m_name, wxPoint((GetSize().x - txt_size.x) / 2, (GetSize().y - txt_size.y) / 2));
 | 
			
		||||
    auto txt_size = dc.GetTextExtent(m_tray_index);
 | 
			
		||||
    auto top = (GetSize().y - MAPPING_ITEM_REAL_SIZE.y) / 2 + FromDIP(8);
 | 
			
		||||
    dc.DrawText(m_tray_index, wxPoint((GetSize().x - txt_size.x) / 2, top));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    top += txt_size.y + FromDIP(7);
 | 
			
		||||
    dc.SetFont(::Label::Body_12);
 | 
			
		||||
    txt_size = dc.GetTextExtent(m_name);
 | 
			
		||||
    dc.DrawText(m_name, wxPoint((GetSize().x - txt_size.x) / 2, top));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MappingItem::set_data(wxColour colour, wxString name, TrayData data, bool unmatch)
 | 
			
		||||
| 
						 | 
				
			
			@ -582,21 +620,32 @@ void MappingItem::doRender(wxDC &dc)
 | 
			
		|||
{
 | 
			
		||||
    dc.SetPen(m_coloul);
 | 
			
		||||
    dc.SetBrush(wxBrush(m_coloul));
 | 
			
		||||
    dc.DrawRoundedRectangle(0, 0, GetSize().x, GetSize().y,5);
 | 
			
		||||
    if (m_coloul == *wxWHITE) {
 | 
			
		||||
        dc.SetPen(wxPen(wxColour(0xAC, 0xAC, 0xAC),1));
 | 
			
		||||
#ifdef __APPLE__
 | 
			
		||||
    dc.DrawRoundedRectangle(1, 1, GetSize().x - 1, GetSize().y - 1, 5);
 | 
			
		||||
#else
 | 
			
		||||
    dc.DrawRoundedRectangle(0, 0, GetSize().x, GetSize().y, 5);
 | 
			
		||||
#endif // __APPLE__
 | 
			
		||||
    dc.DrawRectangle(0, (GetSize().y - MAPPING_ITEM_REAL_SIZE.y) / 2, MAPPING_ITEM_REAL_SIZE.x, MAPPING_ITEM_REAL_SIZE.y);
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
    } 
 | 
			
		||||
//    if (m_coloul == *wxWHITE) {
 | 
			
		||||
//        dc.SetPen(wxPen(wxColour(0xAC, 0xAC, 0xAC), 1));
 | 
			
		||||
//#ifdef __APPLE__
 | 
			
		||||
//        dc.DrawRectangle(1, 1, GetSize().x - 1, GetSize().y - 1);
 | 
			
		||||
//#else
 | 
			
		||||
//        dc.DrawRectangle(0, 0, tray_size.x, tray_size.y);
 | 
			
		||||
//#endif // __APPLE__
 | 
			
		||||
//    }
 | 
			
		||||
 | 
			
		||||
    wxColour side_colour = wxColour(0xE4E4E4);
 | 
			
		||||
 | 
			
		||||
    dc.SetPen(side_colour);
 | 
			
		||||
    dc.SetBrush(wxBrush(side_colour));
 | 
			
		||||
#ifdef __APPLE__
 | 
			
		||||
    dc.DrawRectangle(0, 0, FromDIP(4), GetSize().y);
 | 
			
		||||
    dc.DrawRectangle(GetSize().x - FromDIP(4), 0, FromDIP(4), GetSize().y);
 | 
			
		||||
#else
 | 
			
		||||
    dc.DrawRectangle(0, 0, FromDIP(4), GetSize().y);
 | 
			
		||||
    dc.DrawRectangle(GetSize().x - FromDIP(4), 0, FromDIP(4), GetSize().y);
 | 
			
		||||
#endif // __APPLE__
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AmsMapingTipPopup::AmsMapingTipPopup(wxWindow *parent) 
 | 
			
		||||
    :wxPopupTransientWindow(parent, wxBORDER_NONE)
 | 
			
		||||
    :PopupWindow(parent, wxBORDER_NONE)
 | 
			
		||||
{
 | 
			
		||||
    SetBackgroundColour(*wxWHITE);
 | 
			
		||||
    wxBoxSizer *m_sizer_main = new wxBoxSizer(wxVERTICAL);
 | 
			
		||||
| 
						 | 
				
			
			@ -696,11 +745,11 @@ void AmsMapingTipPopup::paintEvent(wxPaintEvent &evt)
 | 
			
		|||
void AmsMapingTipPopup::OnDismiss() {}
 | 
			
		||||
 | 
			
		||||
bool AmsMapingTipPopup::ProcessLeftDown(wxMouseEvent &event) { 
 | 
			
		||||
    return wxPopupTransientWindow::ProcessLeftDown(event); }
 | 
			
		||||
    return PopupWindow::ProcessLeftDown(event); }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
AmsHumidityTipPopup::AmsHumidityTipPopup(wxWindow* parent)
 | 
			
		||||
    :wxPopupTransientWindow(parent, wxBORDER_NONE)
 | 
			
		||||
    :PopupWindow(parent, wxBORDER_NONE)
 | 
			
		||||
{
 | 
			
		||||
    SetBackgroundColour(*wxWHITE);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -727,7 +776,7 @@ AmsHumidityTipPopup::AmsHumidityTipPopup(wxWindow* parent)
 | 
			
		|||
    m_staticText1->SetFont(::Label::Head_13);
 | 
			
		||||
   
 | 
			
		||||
 | 
			
		||||
    m_staticText2 = new Label(this, _L("Green means that AMS humidity is normal, orange represent humidity is high, red represent humidity is too high.(Hygrometer: lower the better, The bars: higher the better)"));
 | 
			
		||||
    m_staticText2 = new Label(this, _L("Green means that AMS humidity is normal, orange represent humidity is high, red represent humidity is too high.(Hygrometer: lower the better.)"));
 | 
			
		||||
    m_staticText2->SetFont(::Label::Body_13);
 | 
			
		||||
    m_staticText2->SetSize(wxSize(FromDIP(360), -1));
 | 
			
		||||
    m_staticText2->SetMinSize(wxSize(FromDIP(360), -1));
 | 
			
		||||
| 
						 | 
				
			
			@ -740,7 +789,7 @@ AmsHumidityTipPopup::AmsHumidityTipPopup(wxWindow* parent)
 | 
			
		|||
    m_staticText3->SetFont(::Label::Head_13);
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
    m_staticText4 = new Label(this, _L("A desiccant status lower than two bars indicates that desiccant may be inactive. Please change the desiccant. (Higher is better)"));
 | 
			
		||||
    m_staticText4 = new Label(this, _L("A desiccant status lower than two bars indicates that desiccant may be inactive. Please change the desiccant.(The bars: higher the better.)"));
 | 
			
		||||
    m_staticText4->SetFont(::Label::Body_13);
 | 
			
		||||
    m_staticText4->SetSize(wxSize(FromDIP(360), -1));
 | 
			
		||||
    m_staticText4->SetMinSize(wxSize(FromDIP(360), -1));
 | 
			
		||||
| 
						 | 
				
			
			@ -766,9 +815,9 @@ AmsHumidityTipPopup::AmsHumidityTipPopup(wxWindow* parent)
 | 
			
		|||
    main_sizer->Add(m_staticText_note, 0, wxALL | wxLEFT | wxRIGHT, 34);
 | 
			
		||||
 | 
			
		||||
    m_button_confirm = new Button(this, _L("OK"));
 | 
			
		||||
    StateColor btn_bg_green(std::pair<wxColour, int>(wxColour(27, 136, 68), StateColor::Pressed), std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal));
 | 
			
		||||
    StateColor btn_bg_green(std::pair<wxColour, int>(wxColour(27, 136, 68), StateColor::Pressed), std::pair<wxColour, int>(wxColour(0, 174, 66), StateColor::Normal));
 | 
			
		||||
    m_button_confirm->SetBackgroundColor(btn_bg_green);
 | 
			
		||||
    m_button_confirm->SetBorderColor(wxColour(0, 150, 136));
 | 
			
		||||
    m_button_confirm->SetBorderColor(wxColour(0, 174, 66));
 | 
			
		||||
    m_button_confirm->SetTextColor(wxColour(0xFFFFFE));
 | 
			
		||||
    m_button_confirm->SetSize(wxSize(FromDIP(72), FromDIP(24)));
 | 
			
		||||
    m_button_confirm->SetMinSize(wxSize(FromDIP(72), FromDIP(24)));
 | 
			
		||||
| 
						 | 
				
			
			@ -814,11 +863,11 @@ void AmsHumidityTipPopup::paintEvent(wxPaintEvent& evt)
 | 
			
		|||
void AmsHumidityTipPopup::OnDismiss() {}
 | 
			
		||||
 | 
			
		||||
bool AmsHumidityTipPopup::ProcessLeftDown(wxMouseEvent& event) {
 | 
			
		||||
    return wxPopupTransientWindow::ProcessLeftDown(event);
 | 
			
		||||
    return PopupWindow::ProcessLeftDown(event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AmsTutorialPopup::AmsTutorialPopup(wxWindow* parent)
 | 
			
		||||
:wxPopupTransientWindow(parent, wxBORDER_NONE)
 | 
			
		||||
:PopupWindow(parent, wxBORDER_NONE)
 | 
			
		||||
{
 | 
			
		||||
    Bind(wxEVT_PAINT, &AmsTutorialPopup::paintEvent, this);
 | 
			
		||||
    SetBackgroundColour(*wxWHITE);
 | 
			
		||||
| 
						 | 
				
			
			@ -916,12 +965,12 @@ void AmsTutorialPopup::paintEvent(wxPaintEvent& evt)
 | 
			
		|||
void AmsTutorialPopup::OnDismiss() {}
 | 
			
		||||
 | 
			
		||||
bool AmsTutorialPopup::ProcessLeftDown(wxMouseEvent& event) {
 | 
			
		||||
    return wxPopupTransientWindow::ProcessLeftDown(event);
 | 
			
		||||
    return PopupWindow::ProcessLeftDown(event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
AmsIntroducePopup::AmsIntroducePopup(wxWindow* parent)
 | 
			
		||||
:wxPopupTransientWindow(parent, wxBORDER_NONE)
 | 
			
		||||
:PopupWindow(parent, wxBORDER_NONE)
 | 
			
		||||
{
 | 
			
		||||
    Bind(wxEVT_PAINT, &AmsIntroducePopup::paintEvent, this);
 | 
			
		||||
    SetBackgroundColour(*wxWHITE);
 | 
			
		||||
| 
						 | 
				
			
			@ -1001,8 +1050,60 @@ void AmsIntroducePopup::paintEvent(wxPaintEvent& evt)
 | 
			
		|||
void AmsIntroducePopup::OnDismiss() {}
 | 
			
		||||
 | 
			
		||||
bool AmsIntroducePopup::ProcessLeftDown(wxMouseEvent& event) {
 | 
			
		||||
    return wxPopupTransientWindow::ProcessLeftDown(event);
 | 
			
		||||
    return PopupWindow::ProcessLeftDown(event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
MappingContainer::MappingContainer(wxWindow* parent)
 | 
			
		||||
    : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
 | 
			
		||||
{
 | 
			
		||||
#ifdef __WINDOWS__
 | 
			
		||||
    SetDoubleBuffered(true);
 | 
			
		||||
#endif //__WINDOWS__
 | 
			
		||||
    SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
 | 
			
		||||
    Bind(wxEVT_PAINT, &MappingContainer::paintEvent, this);
 | 
			
		||||
 | 
			
		||||
    ams_mapping_item_container = create_scaled_bitmap("ams_mapping_container", this, 78);
 | 
			
		||||
 | 
			
		||||
    SetMinSize(wxSize(FromDIP(230), FromDIP(78)));
 | 
			
		||||
    SetMaxSize(wxSize(FromDIP(230), FromDIP(78)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MappingContainer::~MappingContainer()
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void MappingContainer::paintEvent(wxPaintEvent& evt)
 | 
			
		||||
{
 | 
			
		||||
    wxPaintDC dc(this);
 | 
			
		||||
    render(dc);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MappingContainer::render(wxDC& dc)
 | 
			
		||||
{
 | 
			
		||||
#ifdef __WXMSW__
 | 
			
		||||
    wxSize     size = GetSize();
 | 
			
		||||
    wxMemoryDC memdc;
 | 
			
		||||
    wxBitmap   bmp(size.x, size.y);
 | 
			
		||||
    memdc.SelectObject(bmp);
 | 
			
		||||
    memdc.Blit({ 0, 0 }, size, &dc, { 0, 0 });
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        wxGCDC dc2(memdc);
 | 
			
		||||
        doRender(dc2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    memdc.SelectObject(wxNullBitmap);
 | 
			
		||||
    dc.DrawBitmap(bmp, 0, 0);
 | 
			
		||||
#else
 | 
			
		||||
    doRender(dc);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MappingContainer::doRender(wxDC& dc)
 | 
			
		||||
{
 | 
			
		||||
    dc.DrawBitmap(ams_mapping_item_container, 0, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}} // namespace Slic3r::GUI
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,6 +34,7 @@
 | 
			
		|||
#include "Widgets/CheckBox.hpp"
 | 
			
		||||
#include "Widgets/ComboBox.hpp"
 | 
			
		||||
#include "Widgets/ScrolledWindow.hpp"
 | 
			
		||||
#include "Widgets/PopupWindow.hpp"
 | 
			
		||||
#include <wx/simplebook.h>
 | 
			
		||||
#include <wx/hashmap.h>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -41,6 +42,7 @@ namespace Slic3r { namespace GUI {
 | 
			
		|||
 | 
			
		||||
#define MATERIAL_ITEM_SIZE wxSize(FromDIP(64), FromDIP(34))
 | 
			
		||||
#define MATERIAL_ITEM_REAL_SIZE wxSize(FromDIP(62), FromDIP(32))
 | 
			
		||||
#define MAPPING_ITEM_REAL_SIZE wxSize(FromDIP(48), FromDIP(45))
 | 
			
		||||
#define AMS_TOTAL_COUNT 4
 | 
			
		||||
 | 
			
		||||
enum TrayType {
 | 
			
		||||
| 
						 | 
				
			
			@ -95,8 +97,11 @@ public:
 | 
			
		|||
    MappingItem(wxWindow *parent);
 | 
			
		||||
    ~MappingItem();
 | 
			
		||||
 | 
			
		||||
	void     update_data(TrayData data);
 | 
			
		||||
    void     send_event(int fliament_id);
 | 
			
		||||
	void update_data(TrayData data);
 | 
			
		||||
    void send_event(int fliament_id);
 | 
			
		||||
    void set_tray_index(wxString t_index) {m_tray_index = t_index;};
 | 
			
		||||
 | 
			
		||||
    wxString m_tray_index;
 | 
			
		||||
    wxColour m_coloul;
 | 
			
		||||
    wxString m_name;
 | 
			
		||||
    TrayData m_tray_data;
 | 
			
		||||
| 
						 | 
				
			
			@ -109,7 +114,18 @@ public:
 | 
			
		|||
    void doRender(wxDC &dc);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class AmsMapingPopup : public wxPopupTransientWindow
 | 
			
		||||
class MappingContainer : public wxPanel
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    wxBitmap  ams_mapping_item_container;
 | 
			
		||||
    MappingContainer(wxWindow* parent);
 | 
			
		||||
    ~MappingContainer();
 | 
			
		||||
    void paintEvent(wxPaintEvent& evt);
 | 
			
		||||
    void render(wxDC& dc);
 | 
			
		||||
    void doRender(wxDC& dc);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class AmsMapingPopup : public PopupWindow
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    AmsMapingPopup(wxWindow *parent);
 | 
			
		||||
| 
						 | 
				
			
			@ -118,7 +134,8 @@ public:
 | 
			
		|||
 | 
			
		||||
    wxStaticText *           m_warning_text{nullptr}; 
 | 
			
		||||
    std::vector<std::string> m_materials_list;
 | 
			
		||||
    std::vector<wxBoxSizer*>  m_amsmapping_sizer_list;
 | 
			
		||||
    std::vector<wxBoxSizer*> m_amsmapping_container_sizer_list;
 | 
			
		||||
    std::vector<wxWindow*>   m_amsmapping_container_list;
 | 
			
		||||
    std::vector<MappingItem*> m_mapping_item_list;
 | 
			
		||||
 | 
			
		||||
    bool        m_has_unmatch_filament {false};
 | 
			
		||||
| 
						 | 
				
			
			@ -130,7 +147,7 @@ public:
 | 
			
		|||
    void         update_materials_list(std::vector<std::string> list);
 | 
			
		||||
    void         set_tag_texture(std::string texture);
 | 
			
		||||
    void         update_ams_data(std::map<std::string, Ams *> amsList);
 | 
			
		||||
    void         add_ams_mapping(std::vector<TrayData> tray_data);
 | 
			
		||||
    void         add_ams_mapping(std::vector<TrayData> tray_data, wxWindow* container, wxBoxSizer* sizer);
 | 
			
		||||
    void         set_current_filament_id(int id){m_current_filament_id = id;};
 | 
			
		||||
    int          get_current_filament_id(){return m_current_filament_id;};
 | 
			
		||||
    bool         is_match_material(std::string material);
 | 
			
		||||
| 
						 | 
				
			
			@ -141,7 +158,7 @@ public:
 | 
			
		|||
    std::vector<TrayData> parse_ams_mapping(std::map<std::string, Ams*> amsList);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class AmsMapingTipPopup : public wxPopupTransientWindow
 | 
			
		||||
class AmsMapingTipPopup : public PopupWindow
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    AmsMapingTipPopup(wxWindow *parent);
 | 
			
		||||
| 
						 | 
				
			
			@ -161,7 +178,7 @@ public:
 | 
			
		|||
    wxStaticText *   m_tip_disable_ams;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class AmsHumidityTipPopup : public wxPopupTransientWindow
 | 
			
		||||
class AmsHumidityTipPopup : public PopupWindow
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    AmsHumidityTipPopup(wxWindow* parent);
 | 
			
		||||
| 
						 | 
				
			
			@ -181,7 +198,7 @@ public:
 | 
			
		|||
    Button* m_button_confirm;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class AmsTutorialPopup : public wxPopupTransientWindow
 | 
			
		||||
class AmsTutorialPopup : public PopupWindow
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    Label* text_title;
 | 
			
		||||
| 
						 | 
				
			
			@ -203,7 +220,7 @@ public:
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AmsIntroducePopup : public wxPopupTransientWindow
 | 
			
		||||
class AmsIntroducePopup : public PopupWindow
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    bool          is_enable_ams = {false};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -492,6 +492,7 @@ void AuFile::on_set_cover()
 | 
			
		|||
        thumbnail_img.SaveFile(encode_path(middle_img_path.c_str()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    wxGetApp().plater()->set_plater_dirty(true);
 | 
			
		||||
    auto evt = wxCommandEvent(EVT_AUXILIARY_UPDATE_COVER);
 | 
			
		||||
    evt.SetString(s_default_folders[m_type]);
 | 
			
		||||
    evt.SetEventObject(m_parent);
 | 
			
		||||
| 
						 | 
				
			
			@ -1099,26 +1100,26 @@ void AuxiliaryPanel::update_all_cover()
 | 
			
		|||
     wxBoxSizer *m_sizer_body = new wxBoxSizer(wxVERTICAL);
 | 
			
		||||
     wxBoxSizer *m_sizer_designer = new wxBoxSizer(wxHORIZONTAL);
 | 
			
		||||
 | 
			
		||||
     auto m_text_designer = new wxStaticText(this, wxID_ANY, _L("Author"), wxDefaultPosition, wxSize(120, -1), 0);
 | 
			
		||||
     auto m_text_designer = new wxStaticText(this, wxID_ANY, _L("Author"), wxDefaultPosition, wxSize(180, -1), 0);
 | 
			
		||||
     m_text_designer->Wrap(-1);
 | 
			
		||||
     m_text_designer->SetForegroundColour(*wxBLACK);
 | 
			
		||||
     m_sizer_designer->Add(m_text_designer, 0, wxALIGN_CENTER, 0);
 | 
			
		||||
 | 
			
		||||
     m_input_designer =  new ::TextInput(this, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(450), FromDIP(30)), wxTE_PROCESS_ENTER);
 | 
			
		||||
     m_input_designer->GetTextCtrl()->SetFont(::Label::Body_14);
 | 
			
		||||
     m_input_designer->GetTextCtrl()->SetSize(wxSize(FromDIP(450), FromDIP(22)));
 | 
			
		||||
     m_input_designer->GetTextCtrl()->SetSize(wxSize(FromDIP(450), -1));
 | 
			
		||||
     m_sizer_designer->Add(m_input_designer, 0, wxALIGN_CENTER, 0);
 | 
			
		||||
 | 
			
		||||
     wxBoxSizer *m_sizer_model_name = new wxBoxSizer(wxHORIZONTAL);
 | 
			
		||||
 | 
			
		||||
     auto m_text_model_name = new wxStaticText(this, wxID_ANY, _L("Model Name"), wxDefaultPosition, wxSize(120, -1), 0);
 | 
			
		||||
     auto m_text_model_name = new wxStaticText(this, wxID_ANY, _L("Model Name"), wxDefaultPosition, wxSize(180, -1), 0);
 | 
			
		||||
     m_text_model_name->SetForegroundColour(*wxBLACK);
 | 
			
		||||
     m_text_model_name->Wrap(-1);
 | 
			
		||||
     m_sizer_model_name->Add(m_text_model_name, 0, wxALIGN_CENTER, 0);
 | 
			
		||||
 | 
			
		||||
     m_imput_model_name =  new ::TextInput(this, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition,wxSize(FromDIP(450),FromDIP(30)), wxTE_PROCESS_ENTER);
 | 
			
		||||
     m_imput_model_name->GetTextCtrl()->SetFont(::Label::Body_14);
 | 
			
		||||
     m_imput_model_name->GetTextCtrl()->SetSize(wxSize(FromDIP(450), FromDIP(22)));
 | 
			
		||||
     m_imput_model_name->GetTextCtrl()->SetSize(wxSize(FromDIP(450), -1));
 | 
			
		||||
     m_sizer_model_name->Add(m_imput_model_name, 0, wxALIGN_CENTER, 0);
 | 
			
		||||
 | 
			
		||||
     /*
 | 
			
		||||
| 
						 | 
				
			
			@ -1224,8 +1225,8 @@ void DesignerPanel::update_info()
 | 
			
		|||
 | 
			
		||||
void DesignerPanel::msw_rescale()
 | 
			
		||||
{
 | 
			
		||||
    m_input_designer->GetTextCtrl()->SetSize(wxSize(FromDIP(450), FromDIP(22)));
 | 
			
		||||
    m_imput_model_name->GetTextCtrl()->SetSize(wxSize(FromDIP(450), FromDIP(22)));
 | 
			
		||||
    m_input_designer->GetTextCtrl()->SetSize(wxSize(FromDIP(450), -1));
 | 
			
		||||
    m_imput_model_name->GetTextCtrl()->SetSize(wxSize(FromDIP(450), -1));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}} // namespace Slic3r::GUI
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -119,7 +119,8 @@ void BBLStatusBarSend::set_range(int val)
 | 
			
		|||
 | 
			
		||||
void BBLStatusBarSend::clear_percent()
 | 
			
		||||
{
 | 
			
		||||
    set_percent_text(wxEmptyString);
 | 
			
		||||
    //set_percent_text(wxEmptyString);
 | 
			
		||||
    m_cancelbutton->Hide();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BBLStatusBarSend::show_progress(bool show)
 | 
			
		||||
| 
						 | 
				
			
			@ -235,6 +236,7 @@ void BBLStatusBarSend::set_status_text(const wxString& txt)
 | 
			
		|||
    wxString str;
 | 
			
		||||
    format_text(m_status_text, m_self->FromDIP(280), txt, str);
 | 
			
		||||
    m_status_text->SetLabelText(str);
 | 
			
		||||
    m_self->Layout();
 | 
			
		||||
    //if (is_english_text(str)) m_status_text->Wrap(m_self->FromDIP(280));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -279,6 +281,7 @@ bool BBLStatusBarSend::update_status(wxString &msg, bool &was_cancel, int percen
 | 
			
		|||
 | 
			
		||||
void BBLStatusBarSend::reset()
 | 
			
		||||
{
 | 
			
		||||
    m_cancelbutton->Show();
 | 
			
		||||
    set_status_text("");
 | 
			
		||||
    m_was_cancelled = false;
 | 
			
		||||
    set_progress(0);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -263,7 +263,7 @@ void BBLTopbar::Init(wxFrame* parent)
 | 
			
		|||
    m_publish_disable_bitmap = create_scaled_bitmap("topbar_publish_disable", nullptr, TOPBAR_ICON_SIZE);
 | 
			
		||||
    m_publish_item->SetDisabledBitmap(m_publish_disable_bitmap);
 | 
			
		||||
    this->EnableTool(m_publish_item->GetId(), false);
 | 
			
		||||
    this->AddSpacer(FromDIP(12));
 | 
			
		||||
    this->AddSpacer(FromDIP(4));
 | 
			
		||||
 | 
			
		||||
    /*wxBitmap model_store_bitmap = create_scaled_bitmap("topbar_store", nullptr, TOPBAR_ICON_SIZE);
 | 
			
		||||
    m_model_store_item = this->AddTool(ID_MODEL_STORE, "", model_store_bitmap);
 | 
			
		||||
| 
						 | 
				
			
			@ -271,12 +271,12 @@ void BBLTopbar::Init(wxFrame* parent)
 | 
			
		|||
    */
 | 
			
		||||
 | 
			
		||||
    //this->AddSeparator();
 | 
			
		||||
    this->AddSpacer(FromDIP(6));
 | 
			
		||||
    this->AddSpacer(FromDIP(4));
 | 
			
		||||
 | 
			
		||||
    wxBitmap iconize_bitmap = create_scaled_bitmap("topbar_min", nullptr, TOPBAR_ICON_SIZE);
 | 
			
		||||
    wxAuiToolBarItem* iconize_btn = this->AddTool(wxID_ICONIZE_FRAME, "", iconize_bitmap);
 | 
			
		||||
 | 
			
		||||
    this->AddSpacer(FromDIP(6));
 | 
			
		||||
    this->AddSpacer(FromDIP(4));
 | 
			
		||||
 | 
			
		||||
    maximize_bitmap = create_scaled_bitmap("topbar_max", nullptr, TOPBAR_ICON_SIZE);
 | 
			
		||||
    window_bitmap = create_scaled_bitmap("topbar_win", nullptr, TOPBAR_ICON_SIZE);
 | 
			
		||||
| 
						 | 
				
			
			@ -287,7 +287,7 @@ void BBLTopbar::Init(wxFrame* parent)
 | 
			
		|||
        maximize_btn = this->AddTool(wxID_MAXIMIZE_FRAME, "", maximize_bitmap);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->AddSpacer(FromDIP(6));
 | 
			
		||||
    this->AddSpacer(FromDIP(4));
 | 
			
		||||
 | 
			
		||||
    wxBitmap close_bitmap = create_scaled_bitmap("topbar_close", nullptr, TOPBAR_ICON_SIZE);
 | 
			
		||||
    wxAuiToolBarItem* close_btn = this->AddTool(wxID_CLOSE_FRAME, "", close_bitmap);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -185,14 +185,9 @@ std::string BackgroundSlicingProcess::output_filepath_for_project(const boost::f
 | 
			
		|||
// from the G-code generator.
 | 
			
		||||
void BackgroundSlicingProcess::process_fff()
 | 
			
		||||
{
 | 
			
		||||
	assert(m_print == m_fff_print);
 | 
			
		||||
	PresetBundle &preset_bundle = *wxGetApp().preset_bundle;
 | 
			
		||||
 | 
			
		||||
	m_fff_print->is_BBL_printer() =
 | 
			
		||||
            preset_bundle.printers.get_edited_preset().is_bbl_vendor_preset(
 | 
			
		||||
                &preset_bundle);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
    assert(m_print == m_fff_print);
 | 
			
		||||
    PresetBundle &preset_bundle = *wxGetApp().preset_bundle;
 | 
			
		||||
    m_fff_print->is_BBL_printer() = preset_bundle.printers.get_edited_preset().is_bbl_vendor_preset(&preset_bundle);
 | 
			
		||||
	//BBS: add the logic to process from an existed gcode file
 | 
			
		||||
	if (m_print->finished()) {
 | 
			
		||||
		BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" %1%: skip slicing, to process previous gcode file")%__LINE__;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,6 +16,7 @@
 | 
			
		|||
#include "slic3r/GUI/GUI_App.hpp"
 | 
			
		||||
#include "slic3r/GUI/I18N.hpp"
 | 
			
		||||
#include "slic3r/Utils/Bonjour.hpp"
 | 
			
		||||
#include "Widgets/Button.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -60,6 +61,8 @@ BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech)
 | 
			
		|||
	, timer_state(0)
 | 
			
		||||
	, tech(tech)
 | 
			
		||||
{
 | 
			
		||||
	SetBackgroundColour(*wxWHITE);
 | 
			
		||||
 | 
			
		||||
	const int em = GUI::wxGetApp().em_unit();
 | 
			
		||||
	list->SetMinSize(wxSize(80 * em, 30 * em));
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -78,10 +81,39 @@ BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech)
 | 
			
		|||
 | 
			
		||||
	vsizer->Add(list, 1, wxEXPAND | wxALL, em);
 | 
			
		||||
 | 
			
		||||
	wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL);
 | 
			
		||||
	button_sizer->Add(new wxButton(this, wxID_OK, "OK"), 0, wxALL, em);
 | 
			
		||||
	button_sizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxALL, em);
 | 
			
		||||
	// ^ Note: The Ok/Cancel labels are translated by wxWidgets
 | 
			
		||||
 | 
			
		||||
    auto button_sizer = new wxBoxSizer(wxHORIZONTAL);
 | 
			
		||||
 | 
			
		||||
    StateColor btn_bg_green(std::pair<wxColour, int>(wxColour(27, 136, 68), StateColor::Pressed), std::pair<wxColour, int>(wxColour(61, 203, 115), StateColor::Hovered),
 | 
			
		||||
                            std::pair<wxColour, int>(wxColour(0, 174, 66), StateColor::Normal));
 | 
			
		||||
 | 
			
		||||
    StateColor btn_bg_white(std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed), std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
 | 
			
		||||
                            std::pair<wxColour, int>(*wxWHITE, StateColor::Normal));
 | 
			
		||||
 | 
			
		||||
    auto m_button_ok = new Button(this, _L("OK"));
 | 
			
		||||
    m_button_ok->SetBackgroundColor(btn_bg_green);
 | 
			
		||||
    m_button_ok->SetBorderColor(*wxWHITE);
 | 
			
		||||
    m_button_ok->SetTextColor(*wxWHITE);
 | 
			
		||||
    m_button_ok->SetFont(Label::Body_12);
 | 
			
		||||
    m_button_ok->SetSize(wxSize(FromDIP(58), FromDIP(24)));
 | 
			
		||||
    m_button_ok->SetMinSize(wxSize(FromDIP(58), FromDIP(24)));
 | 
			
		||||
    m_button_ok->SetCornerRadius(FromDIP(12));
 | 
			
		||||
 | 
			
		||||
    m_button_ok->Bind(wxEVT_LEFT_DOWN, [this](auto &e) { this->EndModal(wxID_OK); });
 | 
			
		||||
 | 
			
		||||
    auto m_button_cancel = new Button(this, _L("Cancel"));
 | 
			
		||||
    m_button_cancel->SetBackgroundColor(btn_bg_white);
 | 
			
		||||
    m_button_cancel->SetBorderColor(wxColour(38, 46, 48));
 | 
			
		||||
    m_button_cancel->SetFont(Label::Body_12);
 | 
			
		||||
    m_button_cancel->SetSize(wxSize(FromDIP(58), FromDIP(24)));
 | 
			
		||||
    m_button_cancel->SetMinSize(wxSize(FromDIP(58), FromDIP(24)));
 | 
			
		||||
    m_button_cancel->SetCornerRadius(FromDIP(12));
 | 
			
		||||
 | 
			
		||||
    m_button_cancel->Bind(wxEVT_LEFT_DOWN, [this](auto &e) { this->EndModal(wxID_CANCEL); });
 | 
			
		||||
 | 
			
		||||
    button_sizer->AddStretchSpacer();
 | 
			
		||||
    button_sizer->Add(m_button_ok, 0, wxALL, FromDIP(5));
 | 
			
		||||
    button_sizer->Add(m_button_cancel, 0, wxALL, FromDIP(5));
 | 
			
		||||
 | 
			
		||||
	vsizer->Add(button_sizer, 0, wxALIGN_CENTER);
 | 
			
		||||
	SetSizerAndFit(vsizer);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,7 +33,7 @@ CalibrationDialog::CalibrationDialog(Plater *plater)
 | 
			
		|||
    auto        body_panel = new wxPanel(this, wxID_ANY);
 | 
			
		||||
 | 
			
		||||
    body_panel->SetBackgroundColour(*wxWHITE);
 | 
			
		||||
    auto cali_left_panel = new StaticBox(body_panel, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(303), FromDIP(243)));
 | 
			
		||||
    auto cali_left_panel = new StaticBox(body_panel, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(303), -1));
 | 
			
		||||
    cali_left_panel->SetBackgroundColor(BG_COLOR);
 | 
			
		||||
    cali_left_panel->SetBorderColor(BG_COLOR);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -73,9 +73,7 @@ CalibrationDialog::CalibrationDialog(Plater *plater)
 | 
			
		|||
    cali_left_sizer->Add(0, 0, 0, wxTOP, FromDIP(5));
 | 
			
		||||
 | 
			
		||||
    auto cali_left_text_body =
 | 
			
		||||
        new wxStaticText(cali_left_panel, wxID_ANY,
 | 
			
		||||
                         _L("The calibration program detects the status of your device automatically to minimize deviation.\nIt keeps the device performing optimally."),
 | 
			
		||||
                         wxDefaultPosition, wxSize(FromDIP(260), -1), 0);
 | 
			
		||||
        new Label(cali_left_panel, _L("The calibration program detects the status of your device automatically to minimize deviation.\nIt keeps the device performing optimally."));
 | 
			
		||||
    cali_left_text_body->Wrap(FromDIP(260));
 | 
			
		||||
    cali_left_text_body->SetForegroundColour(wxColour(0x6B, 0x6B, 0x6B));
 | 
			
		||||
    cali_left_text_body->SetBackgroundColour(BG_COLOR);
 | 
			
		||||
| 
						 | 
				
			
			@ -174,7 +172,6 @@ CalibrationDialog::CalibrationDialog(Plater *plater)
 | 
			
		|||
    Fit();
 | 
			
		||||
 | 
			
		||||
    m_calibration_btn->Bind(wxEVT_LEFT_DOWN, &CalibrationDialog::on_start_calibration, this);
 | 
			
		||||
    wxGetApp().UpdateDlgDarkUI(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CalibrationDialog::~CalibrationDialog() {}
 | 
			
		||||
| 
						 | 
				
			
			@ -296,7 +293,10 @@ void CalibrationDialog::update_machine_obj(MachineObject *obj) { m_obj = obj; }
 | 
			
		|||
 | 
			
		||||
bool CalibrationDialog::Show(bool show) 
 | 
			
		||||
{
 | 
			
		||||
    if (show) { CentreOnParent(); }
 | 
			
		||||
    if (show) { 
 | 
			
		||||
        wxGetApp().UpdateDlgDarkUI(this);
 | 
			
		||||
        CentreOnParent(); 
 | 
			
		||||
    }
 | 
			
		||||
    return DPIDialog::Show(show);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,9 +12,9 @@
 | 
			
		|||
namespace Slic3r {
 | 
			
		||||
namespace GUI {
 | 
			
		||||
 | 
			
		||||
wxIMPLEMENT_CLASS(CameraPopup, wxPopupTransientWindow);
 | 
			
		||||
wxIMPLEMENT_CLASS(CameraPopup, PopupWindow);
 | 
			
		||||
 | 
			
		||||
wxBEGIN_EVENT_TABLE(CameraPopup, wxPopupTransientWindow)
 | 
			
		||||
wxBEGIN_EVENT_TABLE(CameraPopup, PopupWindow)
 | 
			
		||||
    EVT_MOUSE_EVENTS(CameraPopup::OnMouse )
 | 
			
		||||
    EVT_SIZE(CameraPopup::OnSize)
 | 
			
		||||
    EVT_SET_FOCUS(CameraPopup::OnSetFocus )
 | 
			
		||||
| 
						 | 
				
			
			@ -24,10 +24,12 @@ wxEND_EVENT_TABLE()
 | 
			
		|||
wxDEFINE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
 | 
			
		||||
wxDEFINE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
 | 
			
		||||
 | 
			
		||||
#define CAMERAPOPUP_CLICK_INTERVAL 20
 | 
			
		||||
 | 
			
		||||
const wxColour TEXT_COL = wxColour(43, 52, 54);
 | 
			
		||||
 | 
			
		||||
CameraPopup::CameraPopup(wxWindow *parent, MachineObject* obj)
 | 
			
		||||
   : wxPopupTransientWindow(parent, wxBORDER_NONE | wxPU_CONTAINS_CONTROLS),
 | 
			
		||||
   : PopupWindow(parent, wxBORDER_NONE | wxPU_CONTAINS_CONTROLS),
 | 
			
		||||
    m_obj(obj)
 | 
			
		||||
{
 | 
			
		||||
#ifdef __WINDOWS__
 | 
			
		||||
| 
						 | 
				
			
			@ -84,10 +86,26 @@ CameraPopup::CameraPopup(wxWindow *parent, MachineObject* obj)
 | 
			
		|||
    main_sizer->Add(top_sizer, 0, wxALL, FromDIP(10));
 | 
			
		||||
 | 
			
		||||
    auto url = wxString::Format(L"https://wiki.bambulab.com/%s/software/bambu-studio/virtual-camera", L"en");
 | 
			
		||||
    vcamera_guide_link = new wxHyperlinkCtrl(m_panel, wxID_ANY, _L("Show \"Live Video\" guide page."),
 | 
			
		||||
        url, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE);
 | 
			
		||||
    auto text = _L("Show \"Live Video\" guide page.");
 | 
			
		||||
 | 
			
		||||
    wxBoxSizer* link_sizer = new wxBoxSizer(wxVERTICAL);
 | 
			
		||||
    vcamera_guide_link = new Label(m_panel, text);
 | 
			
		||||
    vcamera_guide_link->Wrap(-1);
 | 
			
		||||
    vcamera_guide_link->SetForegroundColour(wxColour(0x1F, 0x8E, 0xEA));
 | 
			
		||||
    auto text_size = vcamera_guide_link->GetTextExtent(text);
 | 
			
		||||
    vcamera_guide_link->Bind(wxEVT_LEFT_DOWN, [this, url](wxMouseEvent& e) {wxLaunchDefaultBrowser(url); });
 | 
			
		||||
 | 
			
		||||
    link_underline = new wxPanel(m_panel, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL);
 | 
			
		||||
    link_underline->SetBackgroundColour(wxColour(0x1F, 0x8E, 0xEA));
 | 
			
		||||
    link_underline->SetSize(wxSize(text_size.x, 1));
 | 
			
		||||
    link_underline->SetMinSize(wxSize(text_size.x, 1));
 | 
			
		||||
 | 
			
		||||
    vcamera_guide_link->Hide();
 | 
			
		||||
    main_sizer->Add(vcamera_guide_link, 0, wxALL, FromDIP(15));
 | 
			
		||||
    link_underline->Hide();
 | 
			
		||||
    link_sizer->Add(vcamera_guide_link, 0, wxALL, 0);
 | 
			
		||||
    link_sizer->Add(link_underline, 0, wxALL, 0);
 | 
			
		||||
 | 
			
		||||
    main_sizer->Add(link_sizer, 0, wxALL, FromDIP(15));
 | 
			
		||||
 | 
			
		||||
    m_panel->SetSizer(main_sizer);
 | 
			
		||||
    m_panel->Layout();
 | 
			
		||||
| 
						 | 
				
			
			@ -105,6 +123,10 @@ CameraPopup::CameraPopup(wxWindow *parent, MachineObject* obj)
 | 
			
		|||
    m_panel->Bind(wxEVT_LEFT_UP, &CameraPopup::OnLeftUp, this);
 | 
			
		||||
    #endif //APPLE
 | 
			
		||||
 | 
			
		||||
    this->Bind(wxEVT_TIMER, &CameraPopup::stop_interval, this);
 | 
			
		||||
    m_interval_timer = new wxTimer();
 | 
			
		||||
    m_interval_timer->SetOwner(this);
 | 
			
		||||
 | 
			
		||||
    check_func_supported();
 | 
			
		||||
    wxGetApp().UpdateDarkUIWin(this);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -141,7 +163,9 @@ void CameraPopup::Popup(wxWindow *WXUNUSED(focus))
 | 
			
		|||
    wxSize win_size = this->GetSize();
 | 
			
		||||
    curr_position.x -= win_size.x;
 | 
			
		||||
    this->SetPosition(curr_position);
 | 
			
		||||
    wxPopupTransientWindow::Popup();
 | 
			
		||||
 | 
			
		||||
    if (!m_is_in_interval)
 | 
			
		||||
        PopupWindow::Popup();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
wxWindow* CameraPopup::create_item_radiobox(wxString title, wxWindow* parent, wxString tooltip, int padding_left)
 | 
			
		||||
| 
						 | 
				
			
			@ -225,10 +249,12 @@ void CameraPopup::sync_vcamera_state(bool show_vcamera)
 | 
			
		|||
    if (is_vcamera_show) {
 | 
			
		||||
        m_switch_vcamera->SetValue(true);
 | 
			
		||||
        vcamera_guide_link->Show();
 | 
			
		||||
        link_underline->Show();
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        m_switch_vcamera->SetValue(false);
 | 
			
		||||
        vcamera_guide_link->Hide();
 | 
			
		||||
        link_underline->Hide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    rescale();
 | 
			
		||||
| 
						 | 
				
			
			@ -248,12 +274,15 @@ void CameraPopup::check_func_supported()
 | 
			
		|||
    if (m_obj->is_function_supported(PrinterFunction::FUNC_VIRTUAL_CAMERA) && m_obj->has_ipcam) {
 | 
			
		||||
        m_text_vcamera->Show();
 | 
			
		||||
        m_switch_vcamera->Show();
 | 
			
		||||
        if (is_vcamera_show)
 | 
			
		||||
        if (is_vcamera_show) {
 | 
			
		||||
            vcamera_guide_link->Show();
 | 
			
		||||
            link_underline->Show();
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        m_text_vcamera->Hide();
 | 
			
		||||
        m_switch_vcamera->Hide();
 | 
			
		||||
        vcamera_guide_link->Hide();
 | 
			
		||||
        link_underline->Hide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    allow_alter_resolution = (m_obj->is_function_supported(PrinterFunction::FUNC_ALTER_RESOLUTION) && m_obj->has_ipcam);
 | 
			
		||||
| 
						 | 
				
			
			@ -318,7 +347,7 @@ void CameraPopup::rescale()
 | 
			
		|||
    m_panel->Layout();
 | 
			
		||||
    main_sizer->Fit(m_panel);
 | 
			
		||||
    SetClientSize(m_panel->GetSize());
 | 
			
		||||
    wxPopupTransientWindow::Update();
 | 
			
		||||
    PopupWindow::Update();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CameraPopup::OnLeftUp(wxMouseEvent &event)
 | 
			
		||||
| 
						 | 
				
			
			@ -367,18 +396,31 @@ void CameraPopup::OnLeftUp(wxMouseEvent &event)
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CameraPopup::start_interval()
 | 
			
		||||
{
 | 
			
		||||
    m_interval_timer->Start(CAMERAPOPUP_CLICK_INTERVAL);
 | 
			
		||||
    m_is_in_interval = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CameraPopup::stop_interval(wxTimerEvent& event)
 | 
			
		||||
{
 | 
			
		||||
    m_is_in_interval = false;
 | 
			
		||||
    m_interval_timer->Stop();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CameraPopup::OnDismiss() {
 | 
			
		||||
    wxPopupTransientWindow::OnDismiss();
 | 
			
		||||
    PopupWindow::OnDismiss();
 | 
			
		||||
    this->start_interval();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool CameraPopup::ProcessLeftDown(wxMouseEvent &event)
 | 
			
		||||
{
 | 
			
		||||
    return wxPopupTransientWindow::ProcessLeftDown(event);
 | 
			
		||||
    return PopupWindow::ProcessLeftDown(event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool CameraPopup::Show(bool show)
 | 
			
		||||
{
 | 
			
		||||
    return wxPopupTransientWindow::Show(show);
 | 
			
		||||
    return PopupWindow::Show(show);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CameraPopup::OnSize(wxSizeEvent &event)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,6 +13,7 @@
 | 
			
		|||
#include <wx/hyperlink.h>
 | 
			
		||||
#include "Widgets/SwitchButton.hpp"
 | 
			
		||||
#include "Widgets/RadioBox.hpp"
 | 
			
		||||
#include "Widgets/PopupWindow.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
namespace GUI {
 | 
			
		||||
| 
						 | 
				
			
			@ -20,13 +21,13 @@ namespace GUI {
 | 
			
		|||
wxDECLARE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
 | 
			
		||||
wxDECLARE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
 | 
			
		||||
 | 
			
		||||
class CameraPopup : public wxPopupTransientWindow
 | 
			
		||||
class CameraPopup : public PopupWindow
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    CameraPopup(wxWindow *parent, MachineObject* obj = nullptr);
 | 
			
		||||
    virtual ~CameraPopup() {}
 | 
			
		||||
 | 
			
		||||
    // wxPopupTransientWindow virtual methods are all overridden to log them
 | 
			
		||||
    // PopupWindow virtual methods are all overridden to log them
 | 
			
		||||
    virtual void Popup(wxWindow *focus = NULL) wxOVERRIDE;
 | 
			
		||||
    virtual void OnDismiss() wxOVERRIDE;
 | 
			
		||||
    virtual bool ProcessLeftDown(wxMouseEvent &event) wxOVERRIDE;
 | 
			
		||||
| 
						 | 
				
			
			@ -59,6 +60,8 @@ protected:
 | 
			
		|||
 | 
			
		||||
private:
 | 
			
		||||
    MachineObject* m_obj { nullptr };
 | 
			
		||||
    wxTimer* m_interval_timer{nullptr};
 | 
			
		||||
    bool  m_is_in_interval{ false };
 | 
			
		||||
    wxStaticText* m_text_recording;
 | 
			
		||||
    SwitchButton* m_switch_recording;
 | 
			
		||||
    wxStaticText* m_text_vcamera;
 | 
			
		||||
| 
						 | 
				
			
			@ -70,10 +73,13 @@ private:
 | 
			
		|||
    std::vector<RadioBox*> resolution_rbtns;
 | 
			
		||||
    std::vector<wxStaticText*> resolution_texts;
 | 
			
		||||
    CameraResolution curr_sel_resolution = RESOLUTION_1080P;
 | 
			
		||||
    wxHyperlinkCtrl* vcamera_guide_link { nullptr };
 | 
			
		||||
    Label* vcamera_guide_link { nullptr };
 | 
			
		||||
    wxPanel* link_underline{ nullptr };
 | 
			
		||||
    bool is_vcamera_show = false;
 | 
			
		||||
    bool allow_alter_resolution = false;
 | 
			
		||||
 | 
			
		||||
    void start_interval();
 | 
			
		||||
    void stop_interval(wxTimerEvent& event);
 | 
			
		||||
    void OnMouse(wxMouseEvent &event);
 | 
			
		||||
    void OnSize(wxSizeEvent &event);
 | 
			
		||||
    void OnSetFocus(wxFocusEvent &event);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -430,6 +430,19 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
 | 
			
		|||
        m_support_material_overhangs_queried = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config->opt_bool("enable_support")) {
 | 
			
		||||
        auto   support_type = config->opt_enum<SupportType>("support_type");
 | 
			
		||||
        auto   support_style = config->opt_enum<SupportMaterialStyle>("support_style");
 | 
			
		||||
        std::set<int> enum_set_normal = {0, 1, 2};
 | 
			
		||||
        std::set<int> enum_set_tree   = {0, 3, 4, 5};
 | 
			
		||||
        auto &           set             = is_tree(support_type) ? enum_set_tree : enum_set_normal;
 | 
			
		||||
        if (set.find(support_style) == set.end()) {
 | 
			
		||||
            DynamicPrintConfig new_conf = *config;
 | 
			
		||||
            new_conf.set_key_value("support_style", new ConfigOptionEnum<SupportMaterialStyle>(smsDefault));
 | 
			
		||||
            apply(config, &new_conf);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config->option<ConfigOptionPercent>("sparse_infill_density")->value == 100) {
 | 
			
		||||
        std::string  sparse_infill_pattern            = config->option<ConfigOptionEnum<InfillPattern>>("sparse_infill_pattern")->serialize();
 | 
			
		||||
        const auto  &top_fill_pattern_values = config->def()->get("top_surface_pattern")->enum_values;
 | 
			
		||||
| 
						 | 
				
			
			@ -480,6 +493,17 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config->opt_enum<PrintSequence>("print_sequence") == PrintSequence::ByObject && config->opt_int("skirt_height") > 1 && config->opt_int("skirt_loops") > 0) {
 | 
			
		||||
        const wxString     msg_text = _(L("While printing by Object, the extruder may collide skirt.\nThus, reset the skirt layer to 1 to avoid that."));
 | 
			
		||||
        MessageDialog      dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
 | 
			
		||||
        DynamicPrintConfig new_conf = *config;
 | 
			
		||||
        is_msg_dlg_already_exist    = true;
 | 
			
		||||
        dialog.ShowModal();
 | 
			
		||||
        new_conf.set_key_value("skirt_height", new ConfigOptionInt(1));
 | 
			
		||||
        apply(config, &new_conf);
 | 
			
		||||
        is_msg_dlg_already_exist = false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ConfigManipulation::apply_null_fff_config(DynamicPrintConfig *config, std::vector<std::string> const &keys, std::map<ObjectBase *, ModelConfig *> const &configs)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -148,7 +148,7 @@ void ConnectPrinterDialog::on_button_confirm(wxCommandEvent &event)
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (m_obj) {
 | 
			
		||||
        m_obj->set_access_code(code.ToStdString());
 | 
			
		||||
        m_obj->set_user_access_code(code.ToStdString());
 | 
			
		||||
        wxGetApp().getDeviceManager()->set_selected_machine(m_obj->dev_id);
 | 
			
		||||
    }
 | 
			
		||||
    EndModal(wxID_OK);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -136,6 +136,32 @@ wxColour AmsTray::get_color()
 | 
			
		|||
    return AmsTray::decode_color(color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AmsTray::reset()
 | 
			
		||||
{
 | 
			
		||||
    tag_uid = "";
 | 
			
		||||
    setting_id = "";
 | 
			
		||||
    filament_setting_id = "";
 | 
			
		||||
    type = "";
 | 
			
		||||
    sub_brands = "";
 | 
			
		||||
    color = "";
 | 
			
		||||
    weight = "";
 | 
			
		||||
    diameter = "";
 | 
			
		||||
    temp = "";
 | 
			
		||||
    time = "";
 | 
			
		||||
    bed_temp_type = "";
 | 
			
		||||
    bed_temp = "";
 | 
			
		||||
    nozzle_temp_max = "";
 | 
			
		||||
    nozzle_temp_min = "";
 | 
			
		||||
    xcam_info = "";
 | 
			
		||||
    uuid = "";
 | 
			
		||||
    k = 0.0f;
 | 
			
		||||
    n = 0.0f;
 | 
			
		||||
    is_bbl = false;
 | 
			
		||||
    hold_count = 0;
 | 
			
		||||
    remain = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bool AmsTray::is_tray_info_ready()
 | 
			
		||||
{
 | 
			
		||||
    if (color.empty())
 | 
			
		||||
| 
						 | 
				
			
			@ -302,6 +328,11 @@ std::string MachineObject::get_printer_thumbnail_img_str()
 | 
			
		|||
        return "printer_thumbnail";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string MachineObject::get_ftp_folder()
 | 
			
		||||
{
 | 
			
		||||
    return DeviceManager::get_ftp_folder(printer_type);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MachineObject::set_access_code(std::string code)
 | 
			
		||||
{
 | 
			
		||||
    this->access_code = code;
 | 
			
		||||
| 
						 | 
				
			
			@ -518,6 +549,10 @@ Ams *MachineObject::get_curr_Ams()
 | 
			
		|||
 | 
			
		||||
AmsTray *MachineObject::get_curr_tray()
 | 
			
		||||
{
 | 
			
		||||
    if (m_tray_now.compare(std::to_string(VIRTUAL_TRAY_ID)) == 0) {
 | 
			
		||||
        return &vt_tray;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ams* curr_ams = get_curr_Ams();
 | 
			
		||||
    if (!curr_ams) return nullptr;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -639,17 +674,6 @@ bool MachineObject::is_support_ams_mapping_version(std::string module, std::stri
 | 
			
		|||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MachineObject::is_only_support_cloud_print()
 | 
			
		||||
{
 | 
			
		||||
    auto ap_ver_it = module_vers.find("rv1126");
 | 
			
		||||
    if (ap_ver_it != module_vers.end()) {
 | 
			
		||||
        if (ap_ver_it->second.sw_ver > "00.00.12.61") {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static float calc_color_distance(wxColour c1, wxColour c2)
 | 
			
		||||
{
 | 
			
		||||
    float lab[2][3];
 | 
			
		||||
| 
						 | 
				
			
			@ -1229,7 +1253,7 @@ void MachineObject::parse_state_changed_event()
 | 
			
		|||
{
 | 
			
		||||
    // parse calibration done
 | 
			
		||||
    if (last_mc_print_stage != mc_print_stage) {
 | 
			
		||||
        if (mc_print_stage == 1 && boost::contains(m_gcode_file, "auto_cali_for_user.gcode")) {
 | 
			
		||||
        if (mc_print_stage == 1 && boost::contains(m_gcode_file, "auto_cali_for_user")) {
 | 
			
		||||
            calibration_done = true;
 | 
			
		||||
        } else {
 | 
			
		||||
            calibration_done = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -1322,6 +1346,7 @@ bool MachineObject::is_recording()
 | 
			
		|||
void MachineObject::parse_version_func()
 | 
			
		||||
{
 | 
			
		||||
    auto ota_version = module_vers.find("ota");
 | 
			
		||||
    auto esp32_version = module_vers.find("esp32");
 | 
			
		||||
    if (printer_type == "BL-P001" ||
 | 
			
		||||
        printer_type == "BL-P002") {
 | 
			
		||||
        if (ota_version != module_vers.end()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1348,9 +1373,13 @@ void MachineObject::parse_version_func()
 | 
			
		|||
        }
 | 
			
		||||
    } else if (printer_type == "C11") {
 | 
			
		||||
        local_use_ssl = true;
 | 
			
		||||
        is_cloud_print_only = true;
 | 
			
		||||
        if (ota_version != module_vers.end()) {
 | 
			
		||||
            is_support_send_to_sdcard = ota_version->second.sw_ver.compare("01.02.00.00") >= 0;
 | 
			
		||||
        }
 | 
			
		||||
        if (esp32_version != module_vers.end()) {
 | 
			
		||||
            ams_support_auto_switch_filament_flag = esp32_version->second.sw_ver.compare("00.03.11.50") >= 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1533,20 +1562,10 @@ int MachineObject::command_ams_switch(int tray_index, int old_temp, int new_temp
 | 
			
		|||
 | 
			
		||||
    std::string gcode = "";
 | 
			
		||||
    if (tray_index == 255) {
 | 
			
		||||
        // unload gcode
 | 
			
		||||
        gcode = "M620 S255\nM104 S250\nG28 X\nG91\nG1 Z3.0 F1200\nG90\n"
 | 
			
		||||
                "G1 X70 F12000\nG1 Y245\nG1 Y265 F3000\nM109 S250\nG1 X120 F12000\n"
 | 
			
		||||
                "G1 X20 Y50 F12000\nG1 Y-3\nT255\nM104 S0\nG1 X165 F5000\nG1 Y245\n"
 | 
			
		||||
                "G91\nG1 Z-3.0 F1200\nG90\nM621 S255\n";
 | 
			
		||||
        gcode = DeviceManager::load_gcode(printer_type, "ams_unload.gcode");
 | 
			
		||||
    } else {
 | 
			
		||||
        // load gcode
 | 
			
		||||
        gcode = "M620 S[next_extruder]\nM104 S250\nG28 X\nG91\n\nG1 Z3.0 F1200\nG90\n"
 | 
			
		||||
                "G1 X70 F12000\nG1 Y245\nG1 Y265 F3000\nM109 S250\nG1 X120 F12000\nG1 X20 Y50 F12000\nG1 Y-3"
 | 
			
		||||
                "\nT[next_extruder]\nG1 X54  F12000\nG1 Y265\nM400\nM106 P1 S0\nG92 E0\nG1 E40 F200\nM400"
 | 
			
		||||
                "\nM109 S[new_filament_temp]\nM400\nM106 P1 S255\nG92 E0\nG1 E5 F300\nM400\nM106 P1 S0\nG1 X70  F9000"
 | 
			
		||||
                "\nG1 X76 F15000\nG1 X65 F15000\nG1 X76 F15000\nG1 X65 F15000\nG1 X70 F6000\nG1 X100 F5000\nG1 X70 F15000"
 | 
			
		||||
                "\nG1 X100 F5000\nG1 X70 F15000\nG1 X165 F5000\nG1 Y245\nG91\nG1 Z-3.0 F1200\nG90\nM621 S[next_extruder]\n";
 | 
			
		||||
 | 
			
		||||
        // include VIRTUAL_TRAY_ID
 | 
			
		||||
        gcode = DeviceManager::load_gcode(printer_type, "ams_load.gcode");
 | 
			
		||||
        boost::replace_all(gcode, "[next_extruder]", std::to_string(tray_index));
 | 
			
		||||
        boost::replace_all(gcode, "[new_filament_temp]", std::to_string(new_temp));
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -1649,7 +1668,7 @@ int MachineObject::command_ams_select_tray(std::string tray_id)
 | 
			
		|||
int MachineObject::command_ams_control(std::string action)
 | 
			
		||||
{
 | 
			
		||||
    //valid actions
 | 
			
		||||
    if (action == "resume" || action == "reset" || action == "pause") {
 | 
			
		||||
    if (action == "resume" || action == "reset" || action == "pause" || action == "done") {
 | 
			
		||||
        json j;
 | 
			
		||||
        j["print"]["command"] = "ams_control";
 | 
			
		||||
        j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
 | 
			
		||||
| 
						 | 
				
			
			@ -1840,6 +1859,22 @@ int MachineObject::command_unload_filament()
 | 
			
		|||
        j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
 | 
			
		||||
        return this->publish_json(j.dump());
 | 
			
		||||
    }
 | 
			
		||||
    else if (printer_type == "C11") {
 | 
			
		||||
        std::string gcode = DeviceManager::load_gcode(printer_type, "ams_unload.gcode");
 | 
			
		||||
        if (gcode.empty()) {
 | 
			
		||||
            return -1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        json j;
 | 
			
		||||
        j["print"]["command"] = "gcode_line";
 | 
			
		||||
        j["print"]["param"] = gcode;
 | 
			
		||||
        j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
 | 
			
		||||
 | 
			
		||||
        if (m_agent)
 | 
			
		||||
            j["print"]["user_id"] = m_agent->get_user_id();
 | 
			
		||||
 | 
			
		||||
        return this->publish_json(j.dump());
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        json j;
 | 
			
		||||
        j["print"]["command"] = "unload_filament";
 | 
			
		||||
| 
						 | 
				
			
			@ -2040,6 +2075,9 @@ void MachineObject::reset()
 | 
			
		|||
    last_mc_print_stage = -1;
 | 
			
		||||
    m_new_ver_list_exist = false;
 | 
			
		||||
    extruder_axis_status = LOAD;
 | 
			
		||||
    nozzle_diameter = 0.0f;
 | 
			
		||||
 | 
			
		||||
    vt_tray.reset();
 | 
			
		||||
 | 
			
		||||
    subtask_ = nullptr;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2056,7 +2094,7 @@ int MachineObject::connect(bool is_anonymous)
 | 
			
		|||
    std::string password;
 | 
			
		||||
    if (!is_anonymous) {
 | 
			
		||||
        username = "bblp";
 | 
			
		||||
        password = access_code;
 | 
			
		||||
        password = get_access_code();
 | 
			
		||||
    }
 | 
			
		||||
    if (m_agent) {
 | 
			
		||||
        try {
 | 
			
		||||
| 
						 | 
				
			
			@ -2624,6 +2662,19 @@ int MachineObject::parse_json(std::string payload)
 | 
			
		|||
                    }
 | 
			
		||||
#pragma endregion
 | 
			
		||||
 | 
			
		||||
                    try {
 | 
			
		||||
                        if (jj.contains("nozzle_diameter")) {
 | 
			
		||||
                            if (jj["nozzle_diameter"].is_number_float()) {
 | 
			
		||||
                                nozzle_diameter = jj["nozzle_diameter"].get<float>();
 | 
			
		||||
                            } else if (jj["nozzle_diameter"].is_string()) {
 | 
			
		||||
                                nozzle_diameter = stof(jj["nozzle_diameter"].get<std::string>().c_str());
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    catch(...) {
 | 
			
		||||
                        ;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
#pragma region upgrade
 | 
			
		||||
                    try {
 | 
			
		||||
                        if (jj.contains("upgrade_state")) {
 | 
			
		||||
| 
						 | 
				
			
			@ -2656,29 +2707,36 @@ int MachineObject::parse_json(std::string payload)
 | 
			
		|||
                                        this->command_get_version();
 | 
			
		||||
                                    });
 | 
			
		||||
                                }
 | 
			
		||||
                                upgrade_display_state = jj["upgrade_state"]["dis_state"].get<int>();
 | 
			
		||||
                                if (upgrade_display_hold_count > 0)
 | 
			
		||||
                                    upgrade_display_hold_count--;
 | 
			
		||||
                                else
 | 
			
		||||
                                    upgrade_display_state = jj["upgrade_state"]["dis_state"].get<int>();
 | 
			
		||||
                            } else {
 | 
			
		||||
                                //BBS compatibility with old version
 | 
			
		||||
                                if (upgrade_status == "DOWNLOADING"
 | 
			
		||||
                                    || upgrade_status == "FLASHING"
 | 
			
		||||
                                    || upgrade_status == "UPGRADE_REQUEST"
 | 
			
		||||
                                    || upgrade_status == "PRE_FLASH_START"
 | 
			
		||||
                                    || upgrade_status == "PRE_FLASH_SUCCESS") {
 | 
			
		||||
                                    upgrade_display_state = (int)UpgradingDisplayState::UpgradingInProgress;
 | 
			
		||||
                                }
 | 
			
		||||
                                else if (upgrade_status == "UPGRADE_SUCCESS"
 | 
			
		||||
                                    || upgrade_status == "DOWNLOAD_FAIL"
 | 
			
		||||
                                    || upgrade_status == "FLASH_FAIL"
 | 
			
		||||
                                    || upgrade_status == "PRE_FLASH_FAIL"
 | 
			
		||||
                                    || upgrade_status == "UPGRADE_FAIL") {
 | 
			
		||||
                                    upgrade_display_state = (int)UpgradingDisplayState::UpgradingFinished;
 | 
			
		||||
                                }
 | 
			
		||||
                                if (upgrade_display_hold_count > 0)
 | 
			
		||||
                                    upgrade_display_hold_count--;
 | 
			
		||||
                                else {
 | 
			
		||||
                                    if (upgrade_new_version) {
 | 
			
		||||
                                        upgrade_display_state = (int)UpgradingDisplayState::UpgradingAvaliable;
 | 
			
		||||
                                    //BBS compatibility with old version
 | 
			
		||||
                                    if (upgrade_status == "DOWNLOADING"
 | 
			
		||||
                                        || upgrade_status == "FLASHING"
 | 
			
		||||
                                        || upgrade_status == "UPGRADE_REQUEST"
 | 
			
		||||
                                        || upgrade_status == "PRE_FLASH_START"
 | 
			
		||||
                                        || upgrade_status == "PRE_FLASH_SUCCESS") {
 | 
			
		||||
                                        upgrade_display_state = (int)UpgradingDisplayState::UpgradingInProgress;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    else if (upgrade_status == "UPGRADE_SUCCESS"
 | 
			
		||||
                                        || upgrade_status == "DOWNLOAD_FAIL"
 | 
			
		||||
                                        || upgrade_status == "FLASH_FAIL"
 | 
			
		||||
                                        || upgrade_status == "PRE_FLASH_FAIL"
 | 
			
		||||
                                        || upgrade_status == "UPGRADE_FAIL") {
 | 
			
		||||
                                        upgrade_display_state = (int)UpgradingDisplayState::UpgradingFinished;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    else {
 | 
			
		||||
                                        upgrade_display_state = (int)UpgradingDisplayState::UpgradingUnavaliable;
 | 
			
		||||
                                        if (upgrade_new_version) {
 | 
			
		||||
                                            upgrade_display_state = (int)UpgradingDisplayState::UpgradingAvaliable;
 | 
			
		||||
                                        }
 | 
			
		||||
                                        else {
 | 
			
		||||
                                            upgrade_display_state = (int)UpgradingDisplayState::UpgradingUnavaliable;
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
| 
						 | 
				
			
			@ -3130,6 +3188,89 @@ int MachineObject::parse_json(std::string payload)
 | 
			
		|||
                                    vt_tray.n = jj["vt_tray"]["n"].get<float>();
 | 
			
		||||
                            }
 | 
			
		||||
                            ams_support_virtual_tray = true;
 | 
			
		||||
 | 
			
		||||
                            if (vt_tray.hold_count > 0) {
 | 
			
		||||
                                vt_tray.hold_count--;
 | 
			
		||||
                            } else {
 | 
			
		||||
                                if (jj["vt_tray"].contains("tag_uid"))
 | 
			
		||||
                                    vt_tray.tag_uid = jj["vt_tray"]["tag_uid"].get<std::string>();
 | 
			
		||||
                                else
 | 
			
		||||
                                    vt_tray.tag_uid = "0";
 | 
			
		||||
                                if (jj["vt_tray"].contains("tray_info_idx") && jj["vt_tray"].contains("tray_type")) {
 | 
			
		||||
                                    vt_tray.setting_id = jj["vt_tray"]["tray_info_idx"].get<std::string>();
 | 
			
		||||
                                    std::string type = jj["vt_tray"]["tray_type"].get<std::string>();
 | 
			
		||||
                                    if (vt_tray.setting_id == "GFS00") {
 | 
			
		||||
                                        vt_tray.type = "PLA-S";
 | 
			
		||||
                                    }
 | 
			
		||||
                                    else if (vt_tray.setting_id == "GFS01") {
 | 
			
		||||
                                        vt_tray.type = "PA-S";
 | 
			
		||||
                                    }
 | 
			
		||||
                                    else {
 | 
			
		||||
                                        vt_tray.type = type;
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                                else {
 | 
			
		||||
                                    vt_tray.setting_id = "";
 | 
			
		||||
                                    vt_tray.type = "";
 | 
			
		||||
                                }
 | 
			
		||||
                                if (jj["vt_tray"].contains("tray_sub_brands"))
 | 
			
		||||
                                    vt_tray.sub_brands = jj["vt_tray"]["tray_sub_brands"].get<std::string>();
 | 
			
		||||
                                else
 | 
			
		||||
                                    vt_tray.sub_brands = "";
 | 
			
		||||
                                if (jj["vt_tray"].contains("tray_weight"))
 | 
			
		||||
                                    vt_tray.weight = jj["vt_tray"]["tray_weight"].get<std::string>();
 | 
			
		||||
                                else
 | 
			
		||||
                                    vt_tray.weight = "";
 | 
			
		||||
                                if (jj["vt_tray"].contains("tray_diameter"))
 | 
			
		||||
                                    vt_tray.diameter = jj["vt_tray"]["tray_diameter"].get<std::string>();
 | 
			
		||||
                                else
 | 
			
		||||
                                    vt_tray.diameter = "";
 | 
			
		||||
                                if (jj["vt_tray"].contains("tray_temp"))
 | 
			
		||||
                                    vt_tray.temp = jj["vt_tray"]["tray_temp"].get<std::string>();
 | 
			
		||||
                                else
 | 
			
		||||
                                    vt_tray.temp = "";
 | 
			
		||||
                                if (jj["vt_tray"].contains("tray_time"))
 | 
			
		||||
                                    vt_tray.time = jj["vt_tray"]["tray_time"].get<std::string>();
 | 
			
		||||
                                else
 | 
			
		||||
                                    vt_tray.time = "";
 | 
			
		||||
                                if (jj["vt_tray"].contains("bed_temp_type"))
 | 
			
		||||
                                    vt_tray.bed_temp_type = jj["vt_tray"]["bed_temp_type"].get<std::string>();
 | 
			
		||||
                                else
 | 
			
		||||
                                    vt_tray.bed_temp_type = "";
 | 
			
		||||
                                if (jj["vt_tray"].contains("bed_temp"))
 | 
			
		||||
                                    vt_tray.bed_temp = jj["vt_tray"]["bed_temp"].get<std::string>();
 | 
			
		||||
                                else
 | 
			
		||||
                                    vt_tray.bed_temp = "";
 | 
			
		||||
                                if (jj["vt_tray"].contains("nozzle_temp_max"))
 | 
			
		||||
                                    vt_tray.nozzle_temp_max = jj["vt_tray"]["nozzle_temp_max"].get<std::string>();
 | 
			
		||||
                                else
 | 
			
		||||
                                    vt_tray.nozzle_temp_max = "";
 | 
			
		||||
                                if (jj["vt_tray"].contains("nozzle_temp_min"))
 | 
			
		||||
                                    vt_tray.nozzle_temp_min = jj["vt_tray"]["nozzle_temp_min"].get<std::string>();
 | 
			
		||||
                                else
 | 
			
		||||
                                    vt_tray.nozzle_temp_min = "";
 | 
			
		||||
                                if (jj["vt_tray"].contains("xcam_info"))
 | 
			
		||||
                                    vt_tray.xcam_info = jj["vt_tray"]["xcam_info"].get<std::string>();
 | 
			
		||||
                                else
 | 
			
		||||
                                    vt_tray.xcam_info = "";
 | 
			
		||||
                                if (jj["vt_tray"].contains("tray_uuid"))
 | 
			
		||||
                                    vt_tray.uuid = jj["vt_tray"]["tray_uuid"].get<std::string>();
 | 
			
		||||
                                else
 | 
			
		||||
                                    vt_tray.uuid = "0";
 | 
			
		||||
                                if (jj["vt_tray"].contains("tray_color")) {
 | 
			
		||||
                                    auto color = jj["vt_tray"]["tray_color"].get<std::string>();
 | 
			
		||||
                                    vt_tray.update_color_from_str(color);
 | 
			
		||||
                                }
 | 
			
		||||
                                else {
 | 
			
		||||
                                    vt_tray.color = "";
 | 
			
		||||
                                }
 | 
			
		||||
                                if (jj["vt_tray"].contains("remain")) {
 | 
			
		||||
                                    vt_tray.remain = jj["vt_tray"]["remain"].get<int>();
 | 
			
		||||
                                }
 | 
			
		||||
                                else {
 | 
			
		||||
                                    vt_tray.remain = -1;
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            ams_support_virtual_tray = false;
 | 
			
		||||
                        }
 | 
			
		||||
| 
						 | 
				
			
			@ -3159,24 +3300,39 @@ int MachineObject::parse_json(std::string payload)
 | 
			
		|||
 | 
			
		||||
                    if (jj["ams_id"].is_number()) {
 | 
			
		||||
                        int ams_id = jj["ams_id"].get<int>();
 | 
			
		||||
                        auto ams_it = amsList.find(std::to_string(ams_id));
 | 
			
		||||
                        if (ams_it != amsList.end()) {
 | 
			
		||||
                            int tray_id = jj["tray_id"].get<int>();
 | 
			
		||||
                            auto tray_it = ams_it->second->trayList.find(std::to_string(tray_id));
 | 
			
		||||
                            if (tray_it != ams_it->second->trayList.end()) {
 | 
			
		||||
                                BOOST_LOG_TRIVIAL(trace) << "ams_filament_setting, parse tray info";
 | 
			
		||||
                                tray_it->second->nozzle_temp_max = std::to_string(jj["nozzle_temp_max"].get<int>());
 | 
			
		||||
                                tray_it->second->nozzle_temp_min = std::to_string(jj["nozzle_temp_min"].get<int>());
 | 
			
		||||
                                tray_it->second->type = jj["tray_type"].get<std::string>();
 | 
			
		||||
                                tray_it->second->color = jj["tray_color"].get<std::string>();
 | 
			
		||||
                                tray_it->second->setting_id = jj["tray_info_idx"].get<std::string>();
 | 
			
		||||
                                // delay update
 | 
			
		||||
                                tray_it->second->set_hold_count();
 | 
			
		||||
                            } else {
 | 
			
		||||
                                BOOST_LOG_TRIVIAL(warning) << "ams_filament_setting, can not find in trayList, tray_id=" << tray_id;
 | 
			
		||||
                            }
 | 
			
		||||
                        int tray_id = 0;
 | 
			
		||||
                        if (jj.contains("tray_id")) {
 | 
			
		||||
                            tray_id = jj["tray_id"].get<int>();
 | 
			
		||||
                        }
 | 
			
		||||
                        if (ams_id == 255 && tray_id == VIRTUAL_TRAY_ID) {
 | 
			
		||||
                            BOOST_LOG_TRIVIAL(trace) << "ams_filament_setting, parse tray info";
 | 
			
		||||
                            vt_tray.nozzle_temp_max = std::to_string(jj["nozzle_temp_max"].get<int>());
 | 
			
		||||
                            vt_tray.nozzle_temp_min = std::to_string(jj["nozzle_temp_min"].get<int>());
 | 
			
		||||
                            vt_tray.type = jj["tray_type"].get<std::string>();
 | 
			
		||||
                            vt_tray.color = jj["tray_color"].get<std::string>();
 | 
			
		||||
                            vt_tray.setting_id = jj["tray_info_idx"].get<std::string>();
 | 
			
		||||
                            // delay update
 | 
			
		||||
                            vt_tray.set_hold_count();
 | 
			
		||||
                        } else {
 | 
			
		||||
                            BOOST_LOG_TRIVIAL(warning) << "ams_filament_setting, can not find in amsList, ams_id=" << ams_id;
 | 
			
		||||
                            auto ams_it = amsList.find(std::to_string(ams_id));
 | 
			
		||||
                            if (ams_it != amsList.end()) {
 | 
			
		||||
                                tray_id = jj["tray_id"].get<int>();
 | 
			
		||||
                                auto tray_it = ams_it->second->trayList.find(std::to_string(tray_id));
 | 
			
		||||
                                if (tray_it != ams_it->second->trayList.end()) {
 | 
			
		||||
                                    BOOST_LOG_TRIVIAL(trace) << "ams_filament_setting, parse tray info";
 | 
			
		||||
                                    tray_it->second->nozzle_temp_max = std::to_string(jj["nozzle_temp_max"].get<int>());
 | 
			
		||||
                                    tray_it->second->nozzle_temp_min = std::to_string(jj["nozzle_temp_min"].get<int>());
 | 
			
		||||
                                    tray_it->second->type = jj["tray_type"].get<std::string>();
 | 
			
		||||
                                    tray_it->second->color = jj["tray_color"].get<std::string>();
 | 
			
		||||
                                    tray_it->second->setting_id = jj["tray_info_idx"].get<std::string>();
 | 
			
		||||
                                    // delay update
 | 
			
		||||
                                    tray_it->second->set_hold_count();
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    BOOST_LOG_TRIVIAL(warning) << "ams_filament_setting, can not find in trayList, tray_id=" << tray_id;
 | 
			
		||||
                                }
 | 
			
		||||
                            } else {
 | 
			
		||||
                                BOOST_LOG_TRIVIAL(warning) << "ams_filament_setting, can not find in amsList, ams_id=" << ams_id;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } else if (jj["command"].get<std::string>() == "xcam_control_set") {
 | 
			
		||||
| 
						 | 
				
			
			@ -3336,6 +3492,21 @@ int MachineObject::parse_json(std::string payload)
 | 
			
		|||
            }
 | 
			
		||||
        } catch (...) {}
 | 
			
		||||
 | 
			
		||||
        // upgrade
 | 
			
		||||
        try {
 | 
			
		||||
            if (j.contains("upgrade")) {
 | 
			
		||||
                if (j["upgrade"].contains("command")) {
 | 
			
		||||
                    if (j["upgrade"]["command"].get<std::string>() == "upgrade_confirm") {
 | 
			
		||||
                        this->upgrade_display_state == UpgradingInProgress;
 | 
			
		||||
                        upgrade_display_hold_count = HOLD_COUNT_MAX;
 | 
			
		||||
                        BOOST_LOG_TRIVIAL(info) << "ack of upgrade_confirm";
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (...) {
 | 
			
		||||
            ;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // event info
 | 
			
		||||
        try {
 | 
			
		||||
| 
						 | 
				
			
			@ -3645,6 +3816,7 @@ void DeviceManager::on_machine_alive(std::string json_str)
 | 
			
		|||
            AppConfig* config = Slic3r::GUI::wxGetApp().app_config;
 | 
			
		||||
            if (config) {
 | 
			
		||||
                obj->set_access_code(Slic3r::GUI::wxGetApp().app_config->get("access_code", dev_id));
 | 
			
		||||
                obj->set_user_access_code(Slic3r::GUI::wxGetApp().app_config->get("user_access_code", dev_id));
 | 
			
		||||
            }
 | 
			
		||||
            localMachineList.insert(std::make_pair(dev_id, obj));
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3789,6 +3961,18 @@ bool DeviceManager::set_selected_machine(std::string dev_id)
 | 
			
		|||
    BOOST_LOG_TRIVIAL(info) << "set_selected_machine=" << dev_id;
 | 
			
		||||
    auto my_machine_list = get_my_machine_list();
 | 
			
		||||
    auto it = my_machine_list.find(dev_id);
 | 
			
		||||
 | 
			
		||||
    // disconnect last
 | 
			
		||||
    auto last_selected = my_machine_list.find(selected_machine);
 | 
			
		||||
    if (last_selected != my_machine_list.end()) {
 | 
			
		||||
        if (last_selected->second->connection_type() == "lan") {
 | 
			
		||||
            if (last_selected->second->is_connecting())
 | 
			
		||||
                return false;
 | 
			
		||||
            m_agent->disconnect_printer();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // connect curr
 | 
			
		||||
    if (it != my_machine_list.end()) {
 | 
			
		||||
        if (selected_machine == dev_id) {
 | 
			
		||||
            if (it->second->connection_type() != "lan") {
 | 
			
		||||
| 
						 | 
				
			
			@ -3815,7 +3999,6 @@ bool DeviceManager::set_selected_machine(std::string dev_id)
 | 
			
		|||
                        it->second->reset_update_time();
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    m_agent->disconnect_printer();
 | 
			
		||||
                    it->second->reset();
 | 
			
		||||
                    it->second->connect();
 | 
			
		||||
                    it->second->set_lan_mode_connection_state(true);
 | 
			
		||||
| 
						 | 
				
			
			@ -4007,6 +4190,7 @@ void DeviceManager::load_last_machine()
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
json DeviceManager::function_table = json::object();
 | 
			
		||||
json DeviceManager::filaments_blacklist = json::object();
 | 
			
		||||
 | 
			
		||||
std::string DeviceManager::parse_printer_type(std::string type_str)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -4036,6 +4220,20 @@ std::string DeviceManager::get_printer_display_name(std::string type_str)
 | 
			
		|||
    return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string DeviceManager::get_ftp_folder(std::string type_str)
 | 
			
		||||
{
 | 
			
		||||
    if (DeviceManager::function_table.contains("printers")) {
 | 
			
		||||
        for (auto printer : DeviceManager::function_table["printers"]) {
 | 
			
		||||
            if (printer.contains("model_id") && printer["model_id"].get<std::string>() == type_str) {
 | 
			
		||||
                if (printer.contains("ftp_folder")) {
 | 
			
		||||
                    return printer["ftp_folder"].get<std::string>();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string DeviceManager::get_printer_thumbnail_img(std::string type_str)
 | 
			
		||||
{
 | 
			
		||||
    if (DeviceManager::function_table.contains("printers")) {
 | 
			
		||||
| 
						 | 
				
			
			@ -4115,4 +4313,96 @@ bool DeviceManager::load_functional_config(std::string config_file)
 | 
			
		|||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DeviceManager::load_filaments_blacklist_config(std::string config_file)
 | 
			
		||||
{
 | 
			
		||||
    filaments_blacklist = json::object();
 | 
			
		||||
    std::ifstream json_file(config_file.c_str());
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        if (json_file.is_open()) {
 | 
			
		||||
            json_file >> filaments_blacklist;
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            BOOST_LOG_TRIVIAL(error) << "load filaments blacklist config failed, file = " << config_file;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    catch (...) {
 | 
			
		||||
        BOOST_LOG_TRIVIAL(error) << "load filaments blacklist config failed, file = " << config_file;
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DeviceManager::check_filaments_in_blacklist(std::string tag_vendor, std::string tag_type, bool& in_blacklist, std::string& ac, std::string& info)
 | 
			
		||||
{
 | 
			
		||||
    in_blacklist = false;
 | 
			
		||||
 | 
			
		||||
    if (filaments_blacklist.contains("blacklist")) {
 | 
			
		||||
        for (auto prohibited_filament : filaments_blacklist["blacklist"]) {
 | 
			
		||||
 | 
			
		||||
            std::string vendor;
 | 
			
		||||
            std::string type;
 | 
			
		||||
            std::string action;
 | 
			
		||||
            std::string description;
 | 
			
		||||
 | 
			
		||||
            if (prohibited_filament.contains("vendor") &&
 | 
			
		||||
                prohibited_filament.contains("type") &&
 | 
			
		||||
                prohibited_filament.contains("action") &&
 | 
			
		||||
                prohibited_filament.contains("description"))
 | 
			
		||||
            {
 | 
			
		||||
                vendor = prohibited_filament["vendor"].get<std::string>();
 | 
			
		||||
                type = prohibited_filament["type"].get<std::string>();
 | 
			
		||||
                action = prohibited_filament["action"].get<std::string>();
 | 
			
		||||
                description = prohibited_filament["description"].get<std::string>();
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            std::transform(vendor.begin(), vendor.end(), vendor.begin(), ::tolower);
 | 
			
		||||
            std::transform(tag_vendor.begin(), tag_vendor.end(), tag_vendor.begin(), ::tolower);
 | 
			
		||||
            std::transform(tag_type.begin(), tag_type.end(), tag_type.begin(), ::tolower);
 | 
			
		||||
            std::transform(type.begin(), type.end(), type.begin(), ::tolower);
 | 
			
		||||
 | 
			
		||||
            //third party
 | 
			
		||||
            if (vendor == "third party") {
 | 
			
		||||
                if ("bambulab" != vendor && tag_type == type) {
 | 
			
		||||
                    in_blacklist = true;
 | 
			
		||||
                    ac = action;
 | 
			
		||||
                    info = description;
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                if (vendor == tag_vendor && tag_type == type) {
 | 
			
		||||
                    in_blacklist = true;
 | 
			
		||||
                    ac = action;
 | 
			
		||||
                    info = description;
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string DeviceManager::load_gcode(std::string type_str, std::string gcode_file)
 | 
			
		||||
{
 | 
			
		||||
    std::string gcode_full_path = Slic3r::resources_dir() + "/printers/" + gcode_file;
 | 
			
		||||
    std::ifstream gcode(encode_path(gcode_full_path.c_str()).c_str());
 | 
			
		||||
    try {
 | 
			
		||||
        std::stringstream gcode_str;
 | 
			
		||||
        if (gcode.is_open()) {
 | 
			
		||||
            gcode_str << gcode.rdbuf();
 | 
			
		||||
            gcode.close();
 | 
			
		||||
            return gcode_str.str();
 | 
			
		||||
        }
 | 
			
		||||
    } catch(...) {
 | 
			
		||||
        BOOST_LOG_TRIVIAL(error) << "load gcode file failed, file = " << gcode_file << ", path = " << gcode_full_path;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -178,6 +178,8 @@ public:
 | 
			
		|||
                if (digit1 == -1 || digit2 == -1) break;
 | 
			
		||||
                ret[j] = float(digit1 * 16 + digit2);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            return wxColour(255, 255, 255);
 | 
			
		||||
        }
 | 
			
		||||
        return wxColour(ret[0], ret[1], ret[2]);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -216,6 +218,8 @@ public:
 | 
			
		|||
    void update_color_from_str(std::string color);
 | 
			
		||||
    wxColour get_color();
 | 
			
		||||
 | 
			
		||||
    void reset();
 | 
			
		||||
 | 
			
		||||
    bool is_tray_info_ready();
 | 
			
		||||
    bool is_unset_third_filament();
 | 
			
		||||
    std::string get_display_filament_type();
 | 
			
		||||
| 
						 | 
				
			
			@ -401,15 +405,20 @@ public:
 | 
			
		|||
    std::string dev_ip;
 | 
			
		||||
    std::string dev_id;
 | 
			
		||||
    bool        local_use_ssl { false };
 | 
			
		||||
    float       nozzle_diameter { 0.0f };
 | 
			
		||||
    std::string dev_connection_type;    /* lan | cloud */
 | 
			
		||||
    std::string connection_type() { return dev_connection_type; }
 | 
			
		||||
    void set_dev_ip(std::string ip) {dev_ip = ip;};
 | 
			
		||||
    bool has_access_right() { return !access_code.empty(); }
 | 
			
		||||
    bool has_access_right() { return !get_access_code().empty(); }
 | 
			
		||||
    std::string get_ftp_folder();
 | 
			
		||||
    void set_access_code(std::string code);
 | 
			
		||||
    std::string get_access_code();
 | 
			
		||||
 | 
			
		||||
    void set_user_access_code(std::string code);
 | 
			
		||||
 | 
			
		||||
    std::string get_user_access_code();
 | 
			
		||||
    bool is_lan_mode_printer();
 | 
			
		||||
 | 
			
		||||
    //PRINTER_TYPE printer_type = PRINTER_3DPrinter_UKNOWN;
 | 
			
		||||
    std::string printer_type;       /* model_id */
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -460,7 +469,7 @@ public:
 | 
			
		|||
    int   ams_version = 0;
 | 
			
		||||
    std::string m_ams_id;           // local ams  : "0" ~ "3"
 | 
			
		||||
    std::string m_tray_id;          // local tray id : "0" ~ "3"
 | 
			
		||||
    std::string m_tray_now;         // tray_now : "0" ~ "15" or "255"
 | 
			
		||||
    std::string m_tray_now;         // tray_now : "0" ~ "15" or "254", "255"
 | 
			
		||||
    std::string m_tray_tar;         // tray_tar : "0" ~ "15" or "255"
 | 
			
		||||
 | 
			
		||||
    int extrusion_cali_hold_count = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -484,7 +493,6 @@ public:
 | 
			
		|||
    bool can_unload_filament();
 | 
			
		||||
    bool is_U0_firmware();
 | 
			
		||||
    bool is_support_ams_mapping();
 | 
			
		||||
    bool is_only_support_cloud_print();
 | 
			
		||||
    static bool is_support_ams_mapping_version(std::string module, std::string version);
 | 
			
		||||
 | 
			
		||||
    int ams_filament_mapping(std::vector<FilamentInfo> filaments, std::vector<FilamentInfo> &result, std::vector<int> exclude_id = std::vector<int>());
 | 
			
		||||
| 
						 | 
				
			
			@ -530,6 +538,7 @@ public:
 | 
			
		|||
    bool upgrade_new_version { false };
 | 
			
		||||
    bool upgrade_consistency_request { false };
 | 
			
		||||
    int upgrade_display_state = 0;           // 0 : upgrade unavailable, 1: upgrade idle, 2: upgrading, 3: upgrade_finished
 | 
			
		||||
    int upgrade_display_hold_count = 0;
 | 
			
		||||
    PrinterFirmwareType       firmware_type; // engineer|production
 | 
			
		||||
    std::string upgrade_progress;
 | 
			
		||||
    std::string upgrade_message;
 | 
			
		||||
| 
						 | 
				
			
			@ -630,6 +639,8 @@ public:
 | 
			
		|||
    bool is_support_1080dpi {false};
 | 
			
		||||
    bool is_support_ai_monitoring {false};
 | 
			
		||||
    bool is_support_ams_humidity {true};
 | 
			
		||||
    bool is_support_filament_edit_virtual_tray {true};
 | 
			
		||||
    bool is_cloud_print_only {false};
 | 
			
		||||
 | 
			
		||||
    /* sdcard */
 | 
			
		||||
    MachineObject::SdcardState sdcard_state { NO_SDCARD };
 | 
			
		||||
| 
						 | 
				
			
			@ -827,14 +838,20 @@ public:
 | 
			
		|||
    void load_last_machine();
 | 
			
		||||
 | 
			
		||||
    static json function_table;
 | 
			
		||||
    static json filaments_blacklist;
 | 
			
		||||
 | 
			
		||||
    static std::string parse_printer_type(std::string type_str);
 | 
			
		||||
    static std::string get_printer_display_name(std::string type_str);
 | 
			
		||||
    static std::string get_printer_thumbnail_img(std::string type_str);
 | 
			
		||||
    static std::string get_ftp_folder(std::string type_str);
 | 
			
		||||
    static bool is_function_supported(std::string type_str, std::string function_name);
 | 
			
		||||
    static std::vector<std::string> get_resolution_supported(std::string type_str);
 | 
			
		||||
 | 
			
		||||
    static bool get_bed_temperature_limit(std::string type_str, int& limit);
 | 
			
		||||
    static bool load_functional_config(std::string config_file);
 | 
			
		||||
    static bool load_filaments_blacklist_config(std::string config_file);
 | 
			
		||||
    static void check_filaments_in_blacklist(std::string tag_vendor, std::string tag_type, bool& in_blacklist, std::string& ac, std::string& info);
 | 
			
		||||
    static std::string load_gcode(std::string type_str, std::string gcode_file);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -65,10 +65,10 @@ void ExtrusionCalibration::create()
 | 
			
		|||
#else
 | 
			
		||||
    m_comboBox_nozzle_dia = new ComboBox(m_step_1_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, EXTRUSION_CALIBRATION_BED_COMBOX, 0, nullptr, wxCB_READONLY);
 | 
			
		||||
#endif
 | 
			
		||||
    m_comboBox_nozzle_dia->AppendString("0.2");
 | 
			
		||||
    m_comboBox_nozzle_dia->AppendString("0.4");
 | 
			
		||||
    m_comboBox_nozzle_dia->AppendString("0.6");
 | 
			
		||||
    m_comboBox_nozzle_dia->AppendString("0.8");
 | 
			
		||||
    m_comboBox_nozzle_dia->AppendString(wxString::Format("%1.1f", 0.2));
 | 
			
		||||
    m_comboBox_nozzle_dia->AppendString(wxString::Format("%1.1f", 0.4));
 | 
			
		||||
    m_comboBox_nozzle_dia->AppendString(wxString::Format("%1.1f", 0.6));
 | 
			
		||||
    m_comboBox_nozzle_dia->AppendString(wxString::Format("%1.1f", 0.8));
 | 
			
		||||
 | 
			
		||||
    select_sizer->Add(m_comboBox_nozzle_dia, 0, wxEXPAND);
 | 
			
		||||
    select_sizer->Add(0, EXTRUSION_CALIBRATION_WIDGET_GAP, 0, 0);
 | 
			
		||||
| 
						 | 
				
			
			@ -170,10 +170,10 @@ void ExtrusionCalibration::create()
 | 
			
		|||
 | 
			
		||||
    m_button_cali = new Button(m_step_1_panel, _L("Start calibration"));
 | 
			
		||||
    m_btn_bg_green = StateColor(std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Disabled), std::pair<wxColour, int>(wxColour(27, 136, 68), StateColor::Pressed), std::pair<wxColour, int>(wxColour(61, 203, 115), StateColor::Hovered),
 | 
			
		||||
        std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal));
 | 
			
		||||
        std::pair<wxColour, int>(wxColour(0, 174, 66), StateColor::Normal));
 | 
			
		||||
    m_button_cali->SetBackgroundColor(m_btn_bg_green);
 | 
			
		||||
    m_button_cali->SetFont(Label::Body_13);
 | 
			
		||||
    m_button_cali->SetBorderColor({ std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Disabled), std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Enabled) });
 | 
			
		||||
    m_button_cali->SetBorderColor({ std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Disabled), std::pair<wxColour, int>(wxColour(0, 174, 66), StateColor::Enabled) });
 | 
			
		||||
    m_button_cali->SetTextColor({ std::pair<wxColour, int>(wxColour(172, 172, 172), StateColor::Disabled), std::pair<wxColour, int>(EXTRUSION_CALIBRATION_GREY200, StateColor::Enabled) });
 | 
			
		||||
    m_button_cali->SetCornerRadius(FromDIP(12));
 | 
			
		||||
    m_button_cali->SetMinSize(wxSize(-1, FromDIP(24)));
 | 
			
		||||
| 
						 | 
				
			
			@ -181,9 +181,9 @@ void ExtrusionCalibration::create()
 | 
			
		|||
 | 
			
		||||
    m_cali_cancel = new Button(m_step_1_panel, _L("Cancel"));
 | 
			
		||||
    m_btn_bg_green = StateColor(std::pair<wxColour, int>(wxColour(27, 136, 68), StateColor::Pressed), std::pair<wxColour, int>(wxColour(61, 203, 115), StateColor::Hovered),
 | 
			
		||||
        std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal));
 | 
			
		||||
        std::pair<wxColour, int>(wxColour(0, 174, 66), StateColor::Normal));
 | 
			
		||||
    m_cali_cancel->SetBackgroundColor(m_btn_bg_green);
 | 
			
		||||
    m_cali_cancel->SetBorderColor(wxColour(0, 150, 136));
 | 
			
		||||
    m_cali_cancel->SetBorderColor(wxColour(0, 174, 66));
 | 
			
		||||
    m_cali_cancel->SetTextColor(EXTRUSION_CALIBRATION_GREY200);
 | 
			
		||||
    m_cali_cancel->SetMinSize(EXTRUSION_CALIBRATION_BUTTON_SIZE);
 | 
			
		||||
    m_cali_cancel->SetCornerRadius(FromDIP(12));
 | 
			
		||||
| 
						 | 
				
			
			@ -256,10 +256,10 @@ void ExtrusionCalibration::create()
 | 
			
		|||
    // save button
 | 
			
		||||
    m_button_save_result = new Button(m_step_2_panel, _L("Save"));
 | 
			
		||||
    m_btn_bg_green = StateColor(std::pair<wxColour, int>(wxColour(27, 136, 68), StateColor::Pressed), std::pair<wxColour, int>(wxColour(61, 203, 115), StateColor::Hovered),
 | 
			
		||||
        std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal));
 | 
			
		||||
        std::pair<wxColour, int>(wxColour(0, 174, 66), StateColor::Normal));
 | 
			
		||||
    m_button_save_result->SetBackgroundColor(m_btn_bg_green);
 | 
			
		||||
    m_button_save_result->SetFont(Label::Body_13);
 | 
			
		||||
    m_button_save_result->SetBorderColor(wxColour(0, 150, 136));
 | 
			
		||||
    m_button_save_result->SetBorderColor(wxColour(0, 174, 66));
 | 
			
		||||
    m_button_save_result->SetTextColor(EXTRUSION_CALIBRATION_GREY200);
 | 
			
		||||
    m_button_save_result->SetMinSize(EXTRUSION_CALIBRATION_BUTTON_SIZE);
 | 
			
		||||
    m_button_save_result->SetCornerRadius(FromDIP(12));
 | 
			
		||||
| 
						 | 
				
			
			@ -660,8 +660,6 @@ void ExtrusionCalibration::update_combobox_filaments()
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        for (auto filament_it = preset_bundle->filaments.begin(); filament_it != preset_bundle->filaments.end(); filament_it++) {
 | 
			
		||||
            if (filament_it->setting_id.empty()) continue;
 | 
			
		||||
 | 
			
		||||
            ConfigOption* printer_opt = filament_it->config.option("compatible_printers");
 | 
			
		||||
            ConfigOptionStrings* printer_strs = dynamic_cast<ConfigOptionStrings*>(printer_opt);
 | 
			
		||||
            for (auto printer_str : printer_strs->values) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1021,6 +1021,9 @@ void Choice::BUILD()
 | 
			
		|||
    if (m_opt.height >= 0) size.SetHeight(m_opt.height*m_em_unit);
 | 
			
		||||
    if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit);
 | 
			
		||||
 | 
			
		||||
    if (m_opt.nullable)
 | 
			
		||||
        m_last_meaningful_value = dynamic_cast<ConfigOptionEnumsGenericNullable const *>(m_opt.default_value.get())->get_at(0);
 | 
			
		||||
 | 
			
		||||
	choice_ctrl* temp;
 | 
			
		||||
    auto         dynamic_list = dynamic_lists.find(m_opt.opt_key);
 | 
			
		||||
    if (dynamic_list != dynamic_lists.end())
 | 
			
		||||
| 
						 | 
				
			
			@ -1209,7 +1212,7 @@ void Choice::set_selection()
 | 
			
		|||
 | 
			
		||||
void Choice::set_value(const std::string& value, bool change_event)  //! Redundant?
 | 
			
		||||
{
 | 
			
		||||
	m_disable_change_event = !change_event;
 | 
			
		||||
    m_disable_change_event = !change_event;
 | 
			
		||||
 | 
			
		||||
	size_t idx=0;
 | 
			
		||||
	for (auto el : m_opt.enum_values)
 | 
			
		||||
| 
						 | 
				
			
			@ -1291,10 +1294,10 @@ void Choice::set_value(const boost::any& value, bool change_event)
 | 
			
		|||
        if (m_opt_id.compare("host_type") == 0 && val != 0 &&
 | 
			
		||||
			m_opt.enum_values.size() > field->GetCount()) // for case, when PrusaLink isn't used as a HostType
 | 
			
		||||
			val--;
 | 
			
		||||
		if (m_opt_id == "top_surface_pattern" || m_opt_id == "bottom_surface_pattern" || m_opt_id == "sparse_infill_pattern")
 | 
			
		||||
        if (m_opt_id == "top_surface_pattern" || m_opt_id == "bottom_surface_pattern" || m_opt_id == "sparse_infill_pattern" || m_opt_id == "support_style")
 | 
			
		||||
		{
 | 
			
		||||
			std::string key;
 | 
			
		||||
			const t_config_enum_values& map_names = ConfigOptionEnum<InfillPattern>::get_enum_values();
 | 
			
		||||
			const t_config_enum_values& map_names = *m_opt.enum_keys_map;
 | 
			
		||||
			for (auto it : map_names)
 | 
			
		||||
				if (val == it.second) {
 | 
			
		||||
					key = it.first;
 | 
			
		||||
| 
						 | 
				
			
			@ -1305,6 +1308,12 @@ void Choice::set_value(const boost::any& value, bool change_event)
 | 
			
		|||
			auto it = std::find(values.begin(), values.end(), key);
 | 
			
		||||
			val = it == values.end() ? 0 : it - values.begin();
 | 
			
		||||
		}
 | 
			
		||||
        if (m_opt.nullable) {
 | 
			
		||||
            if (val != ConfigOptionEnumsGenericNullable::nil_value())
 | 
			
		||||
                m_last_meaningful_value = value;
 | 
			
		||||
            else
 | 
			
		||||
                val = -1;
 | 
			
		||||
        }
 | 
			
		||||
		field->SetSelection(val);
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -1370,9 +1379,11 @@ boost::any& Choice::get_value()
 | 
			
		|||
    // BBS
 | 
			
		||||
	if (m_opt.type == coEnum || m_opt.type == coEnums)
 | 
			
		||||
	{
 | 
			
		||||
        if (m_opt_id == "top_surface_pattern" || m_opt_id == "bottom_surface_pattern" || m_opt_id == "sparse_infill_pattern") {
 | 
			
		||||
        if (m_opt.nullable && field->GetSelection() == -1)
 | 
			
		||||
            m_value = ConfigOptionEnumsGenericNullable::nil_value();
 | 
			
		||||
        else if (m_opt_id == "top_surface_pattern" || m_opt_id == "bottom_surface_pattern" || m_opt_id == "sparse_infill_pattern" || m_opt_id == "support_style") {
 | 
			
		||||
			const std::string& key = m_opt.enum_values[field->GetSelection()];
 | 
			
		||||
			m_value = int(ConfigOptionEnum<InfillPattern>::get_enum_values().at(key));
 | 
			
		||||
			m_value = int(m_opt.enum_keys_map->at(key));
 | 
			
		||||
		}
 | 
			
		||||
        // Support ThirdPartyPrinter
 | 
			
		||||
        else if (m_opt_id.compare("host_type") == 0 && m_opt.enum_values.size() > field->GetCount()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1404,6 +1415,20 @@ boost::any& Choice::get_value()
 | 
			
		|||
	return m_value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Choice::set_last_meaningful_value()
 | 
			
		||||
{
 | 
			
		||||
    if (m_opt.nullable) {
 | 
			
		||||
        set_value(m_last_meaningful_value, false);
 | 
			
		||||
        on_change_field();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Choice::set_na_value()
 | 
			
		||||
{
 | 
			
		||||
    dynamic_cast<choice_ctrl *>(window)->SetSelection(-1);
 | 
			
		||||
    on_change_field();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Choice::enable()  { dynamic_cast<choice_ctrl*>(window)->Enable(); }
 | 
			
		||||
void Choice::disable() { dynamic_cast<choice_ctrl*>(window)->Disable(); }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -386,6 +386,9 @@ public:
 | 
			
		|||
	void			set_values(const wxArrayString &values);
 | 
			
		||||
	boost::any&		get_value() override;
 | 
			
		||||
 | 
			
		||||
    void set_last_meaningful_value() override;
 | 
			
		||||
    void set_na_value() override;
 | 
			
		||||
 | 
			
		||||
    void            msw_rescale() override;
 | 
			
		||||
 | 
			
		||||
	void			enable() override ;//{ dynamic_cast<wxBitmapComboBox*>(window)->Enable(); };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -357,7 +357,8 @@ void GCodeViewer::SequentialView::Marker::render(int canvas_width, int canvas_he
 | 
			
		|||
    glsafe(::glDisable(GL_BLEND));
 | 
			
		||||
 | 
			
		||||
    static float last_window_width = 0.0f;
 | 
			
		||||
    static size_t last_text_length = 0;
 | 
			
		||||
    size_t text_line = 0;
 | 
			
		||||
    static size_t last_text_line = 0;
 | 
			
		||||
    const ImU32 text_name_clr = m_is_dark ? IM_COL32(255, 255, 255, 0.88 * 255) : IM_COL32(38, 46, 48, 255);
 | 
			
		||||
    const ImU32 text_value_clr = m_is_dark ? IM_COL32(255, 255, 255, 0.4 * 255) : IM_COL32(144, 144, 144, 255);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -466,6 +467,7 @@ void GCodeViewer::SequentialView::Marker::render(int canvas_width, int canvas_he
 | 
			
		|||
        default:
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        text_line = 2;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        sprintf(buf, "%s%.3f", x.c_str(), position.x() - plate->get_origin().x());
 | 
			
		||||
| 
						 | 
				
			
			@ -485,14 +487,15 @@ void GCodeViewer::SequentialView::Marker::render(int canvas_width, int canvas_he
 | 
			
		|||
        sprintf(buf, "%s%.0f", speed.c_str(), m_curr_move.feedrate);
 | 
			
		||||
        ImGui::PushItemWidth(item_size);
 | 
			
		||||
        imgui.text(buf);
 | 
			
		||||
 | 
			
		||||
        text_line = 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // force extra frame to automatically update window size
 | 
			
		||||
    float window_width = ImGui::GetWindowWidth();
 | 
			
		||||
    //size_t length = strlen(buf);
 | 
			
		||||
    if (window_width != last_window_width /*|| length != last_text_length*/) {
 | 
			
		||||
    if (window_width != last_window_width || text_line != last_text_line) {
 | 
			
		||||
        last_window_width = window_width;
 | 
			
		||||
        //last_text_length = length;
 | 
			
		||||
        last_text_line = text_line;
 | 
			
		||||
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
 | 
			
		||||
        imgui.set_requires_extra_frame();
 | 
			
		||||
#else
 | 
			
		||||
| 
						 | 
				
			
			@ -945,7 +948,6 @@ void GCodeViewer::update_by_mode(ConfigOptionMode mode)
 | 
			
		|||
    // BBS for first layer inspection
 | 
			
		||||
    view_type_items.push_back(EViewType::FilamentId);
 | 
			
		||||
 | 
			
		||||
    options_items.push_back(EMoveType::Seam);
 | 
			
		||||
    options_items.push_back(EMoveType::Travel);
 | 
			
		||||
    options_items.push_back(EMoveType::Retract);
 | 
			
		||||
    options_items.push_back(EMoveType::Unretract);
 | 
			
		||||
| 
						 | 
				
			
			@ -953,6 +955,8 @@ void GCodeViewer::update_by_mode(ConfigOptionMode mode)
 | 
			
		|||
    if (mode == ConfigOptionMode::comDevelop) {
 | 
			
		||||
        options_items.push_back(EMoveType::Tool_change);
 | 
			
		||||
    }
 | 
			
		||||
    //BBS: seam is not real move and extrusion, put at last line
 | 
			
		||||
    options_items.push_back(EMoveType::Seam);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<int> GCodeViewer::get_plater_extruder()
 | 
			
		||||
| 
						 | 
				
			
			@ -1794,11 +1798,13 @@ void GCodeViewer::update_layers_slider_mode()
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
void GCodeViewer::update_marker_curr_move() {
 | 
			
		||||
    auto it = std::find_if(m_gcode_result->moves.begin(), m_gcode_result->moves.end(), [this](auto move) {
 | 
			
		||||
        return move.gcode_id == static_cast<uint64_t>(m_sequential_view.gcode_ids[m_sequential_view.current.last]);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    m_sequential_view.marker.update_curr_move(*it);
 | 
			
		||||
    if ((int)m_last_result_id != -1) {
 | 
			
		||||
        auto it = std::find_if(m_gcode_result->moves.begin(), m_gcode_result->moves.end(), [this](auto move) {
 | 
			
		||||
            return move.gcode_id == static_cast<uint64_t>(m_sequential_view.gcode_ids[m_sequential_view.current.last]);
 | 
			
		||||
            });
 | 
			
		||||
        if (it != m_gcode_result->moves.end())
 | 
			
		||||
            m_sequential_view.marker.update_curr_move(*it);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool GCodeViewer::is_toolpath_move_type_visible(EMoveType type) const
 | 
			
		||||
| 
						 | 
				
			
			@ -3080,6 +3086,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result, const
 | 
			
		|||
    }
 | 
			
		||||
    m_plater_extruder = plater_extruder;
 | 
			
		||||
 | 
			
		||||
    // replace layers for spiral vase mode
 | 
			
		||||
    if (!gcode_result.spiral_vase_layers.empty()) {
 | 
			
		||||
        m_layers.reset();
 | 
			
		||||
        for (const auto& layer : gcode_result.spiral_vase_layers) {
 | 
			
		||||
            m_layers.append(layer.first, { layer.second.first, layer.second.second });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // set layers z range
 | 
			
		||||
    if (!m_layers.empty())
 | 
			
		||||
| 
						 | 
				
			
			@ -3325,11 +3338,13 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
 | 
			
		|||
 | 
			
		||||
    const bool top_layer_only = true;
 | 
			
		||||
 | 
			
		||||
    SequentialView::Endpoints global_endpoints = { m_moves_count , 0 };
 | 
			
		||||
    //BBS
 | 
			
		||||
    SequentialView::Endpoints global_endpoints = { m_sequential_view.gcode_ids.size() , 0 };
 | 
			
		||||
    SequentialView::Endpoints top_layer_endpoints = global_endpoints;
 | 
			
		||||
    SequentialView* sequential_view = const_cast<SequentialView*>(&m_sequential_view);
 | 
			
		||||
    if (top_layer_only || !keep_sequential_current_first) sequential_view->current.first = 0;
 | 
			
		||||
    if (!keep_sequential_current_last) sequential_view->current.last = m_moves_count;
 | 
			
		||||
    //BBS
 | 
			
		||||
    if (!keep_sequential_current_last) sequential_view->current.last = m_sequential_view.gcode_ids.size();
 | 
			
		||||
 | 
			
		||||
    // first pass: collect visible paths and update sequential view data
 | 
			
		||||
    std::vector<std::tuple<unsigned char, unsigned int, unsigned int, unsigned int>> paths;
 | 
			
		||||
| 
						 | 
				
			
			@ -3403,15 +3418,11 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
 | 
			
		|||
    // update current sequential position
 | 
			
		||||
    sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, global_endpoints.first, global_endpoints.last) : global_endpoints.first;
 | 
			
		||||
    if (global_endpoints.last == 0) {
 | 
			
		||||
        m_no_render_path = true;
 | 
			
		||||
         m_no_render_path = true;
 | 
			
		||||
        sequential_view->current.last = global_endpoints.last;
 | 
			
		||||
    } else {
 | 
			
		||||
        m_no_render_path = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!m_no_render_path) {
 | 
			
		||||
        sequential_view->current.last = keep_sequential_current_last ? std::clamp(sequential_view->current.last, global_endpoints.first, global_endpoints.last) : global_endpoints.last;
 | 
			
		||||
    } else {
 | 
			
		||||
        sequential_view->current.last = sequential_view->current.first;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // get the world position from the vertex buffer
 | 
			
		||||
| 
						 | 
				
			
			@ -4125,6 +4136,214 @@ void GCodeViewer::render_shells()
 | 
			
		|||
//    glsafe(::glDepthMask(GL_TRUE));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//BBS
 | 
			
		||||
void GCodeViewer::render_all_plates_stats(const std::vector<const GCodeProcessorResult*>& gcode_result_list, bool show /*= true*/) const {
 | 
			
		||||
    if (!show)
 | 
			
		||||
        return;
 | 
			
		||||
    for (auto gcode_result : gcode_result_list) {
 | 
			
		||||
        if (gcode_result->moves.size() == 0)
 | 
			
		||||
            return;
 | 
			
		||||
    }
 | 
			
		||||
    ImGuiWrapper& imgui = *wxGetApp().imgui();
 | 
			
		||||
 | 
			
		||||
    ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
 | 
			
		||||
    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0, 10.0 * m_scale));
 | 
			
		||||
    ImGui::PushStyleColor(ImGuiCol_Separator, ImVec4(1.0f, 1.0f, 1.0f, 0.6f));
 | 
			
		||||
    ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.00f, 0.68f, 0.26f, 1.0f));
 | 
			
		||||
    ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.00f, 0.68f, 0.26f, 1.0f));
 | 
			
		||||
    ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, ImVec4(0.42f, 0.42f, 0.42f, 1.00f));
 | 
			
		||||
    ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, ImVec4(0.93f, 0.93f, 0.93f, 1.00f));
 | 
			
		||||
    ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, ImVec4(0.93f, 0.93f, 0.93f, 1.00f));
 | 
			
		||||
    ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(340.f * m_scale * imgui.scaled(1.0f / 15.0f), 0));
 | 
			
		||||
 | 
			
		||||
    ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), 0, ImVec2(0.5f, 0.5f));
 | 
			
		||||
    ImGui::Begin(_L("Statistics of All Plates").c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
 | 
			
		||||
    ImDrawList* draw_list = ImGui::GetWindowDrawList();
 | 
			
		||||
 | 
			
		||||
    std::vector<float> filament_diameters = gcode_result_list.front()->filament_diameters;
 | 
			
		||||
    std::vector<float> filament_densities = gcode_result_list.front()->filament_densities;
 | 
			
		||||
    std::vector<Color> filament_colors = decode_colors(wxGetApp().plater()->get_extruder_colors_from_plater_config(gcode_result_list.back()));
 | 
			
		||||
 | 
			
		||||
    bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
 | 
			
		||||
    float window_padding = 4.0f * m_scale;
 | 
			
		||||
    const float icon_size = ImGui::GetTextLineHeight() * 0.7;
 | 
			
		||||
    std::vector<float> offsets;
 | 
			
		||||
    std::map<int, double> volume_of_extruders_all_plates; // map<extruder_idx, volume>
 | 
			
		||||
    std::map<int, double> flushed_volume_of_extruders_all_plates; // map<extruder_idx, flushed volume>
 | 
			
		||||
    std::vector<double> model_used_filaments_m_all_plates;
 | 
			
		||||
    std::vector<double> model_used_filaments_g_all_plates;
 | 
			
		||||
    std::vector<double> flushed_filaments_m_all_plates;
 | 
			
		||||
    std::vector<double> flushed_filaments_g_all_plates;
 | 
			
		||||
    float total_time_all_plates = 0.0f;
 | 
			
		||||
    bool show_detailed_statistics_page = false;
 | 
			
		||||
 | 
			
		||||
    auto max_width = [](const std::vector<std::string>& items, const std::string& title, float extra_size = 0.0f) {
 | 
			
		||||
        float ret = ImGui::CalcTextSize(title.c_str()).x;
 | 
			
		||||
        for (const std::string& item : items) {
 | 
			
		||||
            ret = std::max(ret, extra_size + ImGui::CalcTextSize(item.c_str()).x);
 | 
			
		||||
        }
 | 
			
		||||
        return ret;
 | 
			
		||||
    };
 | 
			
		||||
    auto calculate_offsets = [max_width, window_padding](const std::vector<std::pair<std::string, std::vector<::string>>>& title_columns, float extra_size = 0.0f) {
 | 
			
		||||
        const ImGuiStyle& style = ImGui::GetStyle();
 | 
			
		||||
        std::vector<float> offsets;
 | 
			
		||||
        offsets.push_back(max_width(title_columns[0].second, title_columns[0].first, extra_size) + 3.0f * style.ItemSpacing.x + style.WindowPadding.x);
 | 
			
		||||
        for (size_t i = 1; i < title_columns.size() - 1; i++)
 | 
			
		||||
            offsets.push_back(offsets.back() + max_width(title_columns[i].second, title_columns[i].first) + style.ItemSpacing.x);
 | 
			
		||||
        if (title_columns.back().first == _u8L("Display"))
 | 
			
		||||
            offsets.back() = ImGui::GetWindowWidth() - ImGui::CalcTextSize(_u8L("Display").c_str()).x - ImGui::GetFrameHeight() / 2 - 2 * window_padding;
 | 
			
		||||
 | 
			
		||||
        float average_col_width = ImGui::GetWindowWidth() / static_cast<float>(title_columns.size());
 | 
			
		||||
        std::vector<float> ret;
 | 
			
		||||
        ret.push_back(0);
 | 
			
		||||
        for (size_t i = 1; i < title_columns.size(); i++) {
 | 
			
		||||
            ret.push_back(std::max(offsets[i - 1], i * average_col_width));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return ret;
 | 
			
		||||
    };
 | 
			
		||||
    auto append_item = [icon_size, &imgui, imperial_units, &window_padding, &draw_list, this](const Color& color, const std::vector<std::pair<std::string, float>>& columns_offsets)
 | 
			
		||||
    {
 | 
			
		||||
        // render icon
 | 
			
		||||
        ImVec2 pos = ImVec2(ImGui::GetCursorScreenPos().x + window_padding * 3, ImGui::GetCursorScreenPos().y);
 | 
			
		||||
 | 
			
		||||
        draw_list->AddRectFilled({ pos.x + 1.0f * m_scale, pos.y + 3.0f * m_scale }, { pos.x + icon_size - 1.0f * m_scale, pos.y + icon_size + 1.0f * m_scale },
 | 
			
		||||
            ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }));
 | 
			
		||||
 | 
			
		||||
        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(20.0 * m_scale, 6.0 * m_scale));
 | 
			
		||||
 | 
			
		||||
        // render selectable
 | 
			
		||||
        ImGui::Dummy({ 0.0, 0.0 });
 | 
			
		||||
        ImGui::SameLine();
 | 
			
		||||
 | 
			
		||||
        // render column item
 | 
			
		||||
        {
 | 
			
		||||
            float dummy_size = ImGui::GetStyle().ItemSpacing.x + icon_size;
 | 
			
		||||
            ImGui::SameLine(dummy_size);
 | 
			
		||||
            imgui.text(columns_offsets[0].first);
 | 
			
		||||
 | 
			
		||||
            for (auto i = 1; i < columns_offsets.size(); i++) {
 | 
			
		||||
                ImGui::SameLine(columns_offsets[i].second);
 | 
			
		||||
                imgui.text(columns_offsets[i].first);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ImGui::PopStyleVar(1);
 | 
			
		||||
    };
 | 
			
		||||
    auto append_headers = [&imgui](const std::vector<std::pair<std::string, float>>& title_offsets) {
 | 
			
		||||
        for (size_t i = 0; i < title_offsets.size(); i++) {
 | 
			
		||||
            ImGui::SameLine(title_offsets[i].second);
 | 
			
		||||
            imgui.bold_text(title_offsets[i].first);
 | 
			
		||||
        }
 | 
			
		||||
        ImGui::Separator();
 | 
			
		||||
    };
 | 
			
		||||
    auto get_used_filament_from_volume = [this, imperial_units, &filament_diameters, &filament_densities](double volume, int extruder_id) {
 | 
			
		||||
        double koef = imperial_units ? 1.0 / GizmoObjectManipulation::in_to_mm : 0.001;
 | 
			
		||||
        std::pair<double, double> ret = { koef * volume / (PI * sqr(0.5 * filament_diameters[extruder_id])),
 | 
			
		||||
                                            volume * filament_densities[extruder_id] * 0.001 };
 | 
			
		||||
        return ret;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    ImGui::Dummy({ window_padding, window_padding });
 | 
			
		||||
    ImGui::SameLine();
 | 
			
		||||
    // title and item data
 | 
			
		||||
    {
 | 
			
		||||
        PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list();
 | 
			
		||||
        for (auto plate : plate_list.get_nonempty_plate_list())
 | 
			
		||||
        {
 | 
			
		||||
            auto plate_print_statistics = plate->get_slice_result()->print_statistics;
 | 
			
		||||
            auto plate_extruders = plate->get_extruders(true);
 | 
			
		||||
            for (size_t extruder_id : plate_extruders) {
 | 
			
		||||
                extruder_id -= 1;
 | 
			
		||||
                if (plate_print_statistics.volumes_per_extruder.find(extruder_id) == plate_print_statistics.volumes_per_extruder.end())
 | 
			
		||||
                    continue;
 | 
			
		||||
                double volume = plate_print_statistics.volumes_per_extruder.at(extruder_id);
 | 
			
		||||
                volume_of_extruders_all_plates[extruder_id] += volume;
 | 
			
		||||
                if (plate_print_statistics.flush_per_filament.find(extruder_id) == plate_print_statistics.flush_per_filament.end())
 | 
			
		||||
                    flushed_volume_of_extruders_all_plates[extruder_id] = 0;
 | 
			
		||||
                else {
 | 
			
		||||
                    double flushed_volume = plate_print_statistics.flush_per_filament.at(extruder_id);
 | 
			
		||||
                    flushed_volume_of_extruders_all_plates[extruder_id] += flushed_volume;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            const PrintEstimatedStatistics::Mode& plate_time_mode = plate_print_statistics.modes[static_cast<size_t>(m_time_estimate_mode)];
 | 
			
		||||
            total_time_all_plates += plate_time_mode.time;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (auto it = volume_of_extruders_all_plates.begin(); it != volume_of_extruders_all_plates.end(); it++) {
 | 
			
		||||
            auto [model_used_filament_m, model_used_filament_g] = get_used_filament_from_volume(it->second, it->first);
 | 
			
		||||
            model_used_filaments_m_all_plates.push_back(model_used_filament_m);
 | 
			
		||||
            model_used_filaments_g_all_plates.push_back(model_used_filament_g);
 | 
			
		||||
        }
 | 
			
		||||
        for (auto it = flushed_volume_of_extruders_all_plates.begin(); it != flushed_volume_of_extruders_all_plates.end(); it++) {
 | 
			
		||||
            auto [flushed_filament_m, flushed_filament_g] = get_used_filament_from_volume(it->second, it->first);
 | 
			
		||||
            if (flushed_filament_m != 0.0 || flushed_filament_g != 0.0)
 | 
			
		||||
                show_detailed_statistics_page = true;
 | 
			
		||||
            flushed_filaments_m_all_plates.push_back(flushed_filament_m);
 | 
			
		||||
            flushed_filaments_g_all_plates.push_back(flushed_filament_g);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        char buff[64];
 | 
			
		||||
        double longest_str = 0.0;
 | 
			
		||||
        for (auto i : model_used_filaments_g_all_plates) {
 | 
			
		||||
            if (i > longest_str)
 | 
			
		||||
                longest_str = i;
 | 
			
		||||
        }
 | 
			
		||||
        ::sprintf(buff, "%.2f", longest_str);
 | 
			
		||||
        offsets = calculate_offsets({ {_u8L("Filament"), {""}}, {_u8L("Model"), {buff}}, {_u8L("Flushed"), {buff}}, /*{_u8L("Tower"), total_filaments},*/ {_u8L("Total"), {buff}} }, icon_size);
 | 
			
		||||
 | 
			
		||||
        if (!show_detailed_statistics_page)
 | 
			
		||||
            append_headers({ {_u8L("Filament"), offsets[0]}, {_u8L("Model"), offsets[2]} });
 | 
			
		||||
        else
 | 
			
		||||
            append_headers({ {_u8L("Filament"), offsets[0]}, {_u8L("Model"), offsets[1]}, {_u8L("Flushed"), offsets[2]}, /*{_u8L("Tower"), offsets[3]},*/ {_u8L("Total"), offsets[3]} });// to add Tower
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // item
 | 
			
		||||
    {
 | 
			
		||||
        size_t i = 0;
 | 
			
		||||
        for (auto it = volume_of_extruders_all_plates.begin(); it != volume_of_extruders_all_plates.end(); it++) {
 | 
			
		||||
            if (i < model_used_filaments_m_all_plates.size() && i < model_used_filaments_g_all_plates.size()) {
 | 
			
		||||
                std::vector<std::pair<std::string, float>> columns_offsets;
 | 
			
		||||
                columns_offsets.push_back({ std::to_string(it->first + 1), offsets[0] });
 | 
			
		||||
 | 
			
		||||
                char buf[64];
 | 
			
		||||
                if (show_detailed_statistics_page) {
 | 
			
		||||
                    ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", model_used_filaments_m_all_plates[i], model_used_filaments_g_all_plates[i]);
 | 
			
		||||
                    columns_offsets.push_back({ buf, offsets[1] });
 | 
			
		||||
 | 
			
		||||
                    ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", flushed_filaments_m_all_plates[i], flushed_filaments_g_all_plates[i]);
 | 
			
		||||
                    columns_offsets.push_back({ buf, offsets[2] });
 | 
			
		||||
 | 
			
		||||
                    ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", model_used_filaments_m_all_plates[i] + flushed_filaments_m_all_plates[i], model_used_filaments_g_all_plates[i] + flushed_filaments_g_all_plates[i]);
 | 
			
		||||
                    columns_offsets.push_back({ buf, offsets[3] });
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    ::sprintf(buf, imperial_units ? "%.2f in    %.2f oz" : "%.2f m    %.2f g", model_used_filaments_m_all_plates[i], model_used_filaments_g_all_plates[i]);
 | 
			
		||||
                    columns_offsets.push_back({ buf, offsets[2] });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                append_item(filament_colors[it->first], columns_offsets);
 | 
			
		||||
            }
 | 
			
		||||
            i++;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ImGui::Dummy(ImVec2(0.0f, ImGui::GetFontSize() * 0.1));
 | 
			
		||||
        ImGui::Dummy({ window_padding, window_padding });
 | 
			
		||||
        ImGui::SameLine();
 | 
			
		||||
        imgui.title(_u8L("Total Time Estimation"));
 | 
			
		||||
 | 
			
		||||
        ImGui::Dummy({ window_padding, window_padding });
 | 
			
		||||
        ImGui::SameLine();
 | 
			
		||||
        imgui.text(_u8L("Total time") + ":");
 | 
			
		||||
        ImGui::SameLine();
 | 
			
		||||
        imgui.text(short_time(get_time_dhms(total_time_all_plates)));
 | 
			
		||||
    }
 | 
			
		||||
    ImGui::End();
 | 
			
		||||
    ImGui::PopStyleColor(6);
 | 
			
		||||
    ImGui::PopStyleVar(3);
 | 
			
		||||
    return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canvas_height, int right_margin)
 | 
			
		||||
{
 | 
			
		||||
    if (!m_legend_enabled)
 | 
			
		||||
| 
						 | 
				
			
			@ -4283,8 +4502,6 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
 | 
			
		|||
            const float step_size = range.step_size();
 | 
			
		||||
            for (int i = static_cast<int>(Range_Colors.size()) - 1; i >= 0; --i) {
 | 
			
		||||
                append_range_item(i, range.get_value_at_step(i), decimals);
 | 
			
		||||
                
 | 
			
		||||
                
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			@ -4376,6 +4593,11 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
 | 
			
		|||
        return (it != time_mode.roles_times.end()) ? std::make_pair(it->second, it->second / time_mode.time) : std::make_pair(0.0f, 0.0f);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    auto move_time_and_percent = [time_mode](EMoveType move_type) {
 | 
			
		||||
        auto it = std::find_if(time_mode.moves_times.begin(), time_mode.moves_times.end(), [move_type](const std::pair<EMoveType, float>& item) { return move_type == item.first; });
 | 
			
		||||
        return (it != time_mode.moves_times.end()) ? std::make_pair(it->second, it->second / time_mode.time) : std::make_pair(0.0f, 0.0f);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    auto used_filament_per_role = [this, imperial_units](ExtrusionRole role) {
 | 
			
		||||
        auto it = m_print_statistics.used_filaments_per_role.find(role);
 | 
			
		||||
        if (it == m_print_statistics.used_filaments_per_role.end())
 | 
			
		||||
| 
						 | 
				
			
			@ -4385,6 +4607,14 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
 | 
			
		|||
        return std::make_pair(it->second.first * koef, it->second.second);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // get used filament (meters and grams) from used volume in respect to the active extruder
 | 
			
		||||
    auto get_used_filament_from_volume = [this, imperial_units](double volume, int extruder_id) {
 | 
			
		||||
        double koef = imperial_units ? 1.0 / GizmoObjectManipulation::in_to_mm : 0.001;
 | 
			
		||||
        std::pair<double, double> ret = { koef * volume / (PI * sqr(0.5 * m_filament_diameters[extruder_id])),
 | 
			
		||||
                                          volume * m_filament_densities[extruder_id] * 0.001 };
 | 
			
		||||
        return ret;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    //BBS display Color Scheme
 | 
			
		||||
    ImGui::Dummy({ window_padding, window_padding });
 | 
			
		||||
    ImGui::Dummy({ window_padding, window_padding });
 | 
			
		||||
| 
						 | 
				
			
			@ -4449,7 +4679,9 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
 | 
			
		|||
    std::vector<float> offsets;
 | 
			
		||||
    std::vector<std::string> labels;
 | 
			
		||||
    std::vector<std::string> times;
 | 
			
		||||
    std::string travel_time;
 | 
			
		||||
    std::vector<std::string> percents;
 | 
			
		||||
    std::string travel_percent;
 | 
			
		||||
    std::vector<double> model_used_filaments_m;
 | 
			
		||||
    std::vector<double> model_used_filaments_g;
 | 
			
		||||
    double total_model_used_filament_m = 0, total_model_used_filament_g = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -4462,13 +4694,6 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
 | 
			
		|||
    double koef = imperial_units ? GizmoObjectManipulation::in_to_mm : 1000.0;
 | 
			
		||||
    double unit_conver = imperial_units ? GizmoObjectManipulation::oz_to_g : 1;
 | 
			
		||||
 | 
			
		||||
    // get used filament (meters and grams) from used volume in respect to the active extruder
 | 
			
		||||
    auto get_used_filament_from_volume = [this, imperial_units](double volume, int extruder_id) {
 | 
			
		||||
        double koef = imperial_units ? 1.0 / GizmoObjectManipulation::in_to_mm : 0.001;
 | 
			
		||||
        std::pair<double, double> ret = { koef * volume / (PI * sqr(0.5 * m_filament_diameters[extruder_id])),
 | 
			
		||||
                                          volume * m_filament_densities[extruder_id] * 0.001 };
 | 
			
		||||
        return ret;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // extrusion paths section -> title
 | 
			
		||||
    ImGui::Dummy({ window_padding, window_padding });
 | 
			
		||||
| 
						 | 
				
			
			@ -4496,6 +4721,17 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //BBS: get travel time and percent
 | 
			
		||||
        {
 | 
			
		||||
            auto [time, percent] = move_time_and_percent(EMoveType::Travel);
 | 
			
		||||
            travel_time = (time > 0.0f) ? short_time(get_time_dhms(time)) : "";
 | 
			
		||||
            if (percent == 0)
 | 
			
		||||
                ::sprintf(buffer, "0%%");
 | 
			
		||||
            else
 | 
			
		||||
                percent > 0.001 ? ::sprintf(buffer, "%.1f%%", percent * 100) : ::sprintf(buffer, "<0.1%%");
 | 
			
		||||
            travel_percent = buffer;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        offsets = calculate_offsets({ {_u8L("Line Type"), labels}, {_u8L("Time"), times}, {_u8L("Percent"), percents}, {_u8L("Display"), {""}}}, icon_size);
 | 
			
		||||
        append_headers({{_u8L("Line Type"), offsets[0]}, {_u8L("Time"), offsets[1]}, {_u8L("Percent"), offsets[2]}, {_u8L("Display"), offsets[3]}});
 | 
			
		||||
        break;
 | 
			
		||||
| 
						 | 
				
			
			@ -4584,7 +4820,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
 | 
			
		|||
        };
 | 
			
		||||
        const bool visible = m_buffers[buffer_id(type)].visible;
 | 
			
		||||
        if (type == EMoveType::Travel) {
 | 
			
		||||
            //TODO display travel time
 | 
			
		||||
            //BBS: only display travel time in FeatureType view
 | 
			
		||||
            append_option_item_with_type(type, Travel_Colors[0], _u8L("Travel"), visible);
 | 
			
		||||
        }
 | 
			
		||||
        else if (type == EMoveType::Seam)
 | 
			
		||||
| 
						 | 
				
			
			@ -4624,7 +4860,23 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        for(auto item : options_items) {
 | 
			
		||||
            append_option_item(item, offsets);
 | 
			
		||||
            if (item != EMoveType::Travel) {
 | 
			
		||||
                append_option_item(item, offsets);
 | 
			
		||||
            } else {
 | 
			
		||||
                //BBS: show travel time in FeatureType view
 | 
			
		||||
                const bool visible = m_buffers[buffer_id(item)].visible;
 | 
			
		||||
                std::vector<std::pair<std::string, float>> columns_offsets;
 | 
			
		||||
                columns_offsets.push_back({ _u8L("Travel"), offsets[0] });
 | 
			
		||||
                columns_offsets.push_back({ travel_time, offsets[1] });
 | 
			
		||||
                columns_offsets.push_back({ travel_percent, offsets[2] });
 | 
			
		||||
                append_item(EItemType::Rect, Travel_Colors[0], columns_offsets, true, visible, [this, item, visible]() {
 | 
			
		||||
                        m_buffers[buffer_id(item)].visible = !m_buffers[buffer_id(item)].visible;
 | 
			
		||||
                        // update buffers' render paths
 | 
			
		||||
                        refresh_render_paths(false, false);
 | 
			
		||||
                        update_moves_slider();
 | 
			
		||||
                        wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
 | 
			
		||||
                    });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -4668,7 +4920,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
 | 
			
		|||
    }
 | 
			
		||||
    case EViewType::ColorPrint:
 | 
			
		||||
    {
 | 
			
		||||
        const std::vector<CustomGCode::Item>& custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z;
 | 
			
		||||
        //BBS: replace model custom gcode with current plate custom gcode
 | 
			
		||||
        const std::vector<CustomGCode::Item>& custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().get_curr_plate_custom_gcodes().gcodes : m_custom_gcode_per_print_z;
 | 
			
		||||
        size_t total_items = 1;
 | 
			
		||||
        for (size_t extruder_id : m_extruder_ids) {
 | 
			
		||||
            total_items += color_print_ranges(extruder_id, custom_gcode_per_print_z).size();
 | 
			
		||||
| 
						 | 
				
			
			@ -4722,13 +4975,13 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
 | 
			
		|||
 | 
			
		||||
                        char buf[64];
 | 
			
		||||
                        if (show_flushed_filaments) {
 | 
			
		||||
                            ::sprintf(buf, imperial_units ? "%.2f in\n%.2f g" : "%.2f m\n%.2f g", model_used_filaments_m[i], model_used_filaments_g[i]);
 | 
			
		||||
                            ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", model_used_filaments_m[i], model_used_filaments_g[i]);
 | 
			
		||||
                            columns_offsets.push_back({ buf, offsets[1] });
 | 
			
		||||
 | 
			
		||||
                            ::sprintf(buf, imperial_units ? "%.2f in\n%.2f g" : "%.2f m\n%.2f g", flushed_filaments_m[i], flushed_filaments_g[i]);
 | 
			
		||||
                            ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", flushed_filaments_m[i], flushed_filaments_g[i]);
 | 
			
		||||
                            columns_offsets.push_back({ buf, offsets[2] });
 | 
			
		||||
 | 
			
		||||
                            ::sprintf(buf, imperial_units ? "%.2f in\n%.2f g" : "%.2f m\n%.2f g", model_used_filaments_m[i] + flushed_filaments_m[i], model_used_filaments_g[i] + flushed_filaments_g[i]);
 | 
			
		||||
                            ::sprintf(buf, imperial_units ? "%.2f in\n%.2f oz" : "%.2f m\n%.2f g", model_used_filaments_m[i] + flushed_filaments_m[i], model_used_filaments_g[i] + flushed_filaments_g[i]);
 | 
			
		||||
                            columns_offsets.push_back({ buf, offsets[3] });
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
| 
						 | 
				
			
			@ -4850,7 +5103,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
 | 
			
		|||
        auto generate_partial_times = [this, get_used_filament_from_volume](const TimesList& times, const std::vector<double>& used_filaments) {
 | 
			
		||||
            PartialTimes items;
 | 
			
		||||
 | 
			
		||||
            std::vector<CustomGCode::Item> custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z;
 | 
			
		||||
            //BBS: replace model custom gcode with current plate custom gcode
 | 
			
		||||
            std::vector<CustomGCode::Item> custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().get_curr_plate_custom_gcodes().gcodes : m_custom_gcode_per_print_z;
 | 
			
		||||
            std::vector<Color> last_color(m_extruders_count);
 | 
			
		||||
            for (size_t i = 0; i < m_extruders_count; ++i) {
 | 
			
		||||
                last_color[i] = m_tools.m_tool_colors[i];
 | 
			
		||||
| 
						 | 
				
			
			@ -5220,19 +5474,19 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
 | 
			
		|||
            auto it = std::find_if(time_mode.roles_times.begin(), time_mode.roles_times.end(), [role](const std::pair<ExtrusionRole, float>& item) { return role == item.first; });
 | 
			
		||||
            return (it != time_mode.roles_times.end()) ? it->second : 0.0f;
 | 
			
		||||
        };
 | 
			
		||||
        //BBS: start gcode is prepeare time
 | 
			
		||||
        if (role_time(erCustom) != 0.0f) {
 | 
			
		||||
        //BBS: start gcode is mostly same with prepeare time
 | 
			
		||||
        if (time_mode.prepare_time != 0.0f) {
 | 
			
		||||
            ImGui::Dummy({ window_padding, window_padding });
 | 
			
		||||
            ImGui::SameLine();
 | 
			
		||||
            imgui.text(prepare_str + ":");
 | 
			
		||||
            ImGui::SameLine(max_len);
 | 
			
		||||
            imgui.text(short_time(get_time_dhms(role_time(erCustom))));
 | 
			
		||||
            imgui.text(short_time(get_time_dhms(time_mode.prepare_time)));
 | 
			
		||||
        }
 | 
			
		||||
        ImGui::Dummy({ window_padding, window_padding });
 | 
			
		||||
        ImGui::SameLine();
 | 
			
		||||
        imgui.text(print_str + ":");
 | 
			
		||||
        ImGui::SameLine(max_len);
 | 
			
		||||
        imgui.text(short_time(get_time_dhms(time_mode.time - role_time(erCustom))));
 | 
			
		||||
        imgui.text(short_time(get_time_dhms(time_mode.time - time_mode.prepare_time)));
 | 
			
		||||
        ImGui::Dummy({ window_padding, window_padding });
 | 
			
		||||
        ImGui::SameLine();
 | 
			
		||||
        imgui.text(total_str + ":");
 | 
			
		||||
| 
						 | 
				
			
			@ -5264,16 +5518,16 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
 | 
			
		|||
        }
 | 
			
		||||
        default : { assert(false); break; }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        if (m_view_type == EViewType::ColorPrint) {
 | 
			
		||||
            ImGui::Spacing();
 | 
			
		||||
            ImGui::Dummy({ window_padding, window_padding });
 | 
			
		||||
            ImGui::SameLine();
 | 
			
		||||
            offsets = calculate_offsets({ { _u8L("Options"), { ""}}, { _u8L("Display"), {""}} }, icon_size);
 | 
			
		||||
            append_headers({ {_u8L("Options"), offsets[0] }, { _u8L("Display"), offsets[1]} });
 | 
			
		||||
            for (auto item : options_items)
 | 
			
		||||
                append_option_item(item, offsets);
 | 
			
		||||
        }
 | 
			
		||||
    if (m_view_type == EViewType::ColorPrint) {
 | 
			
		||||
        ImGui::Spacing();
 | 
			
		||||
        ImGui::Dummy({ window_padding, window_padding });
 | 
			
		||||
        ImGui::SameLine();
 | 
			
		||||
        offsets = calculate_offsets({ { _u8L("Options"), { ""}}, { _u8L("Display"), {""}} }, icon_size);
 | 
			
		||||
        append_headers({ {_u8L("Options"), offsets[0] }, { _u8L("Display"), offsets[1]} });
 | 
			
		||||
        for (auto item : options_items)
 | 
			
		||||
            append_option_item(item, offsets);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    legend_height = ImGui::GetCurrentWindow()->Size.y;
 | 
			
		||||
| 
						 | 
				
			
			@ -5304,7 +5558,6 @@ void GCodeViewer::pop_combo_style()
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
void GCodeViewer::render_slider(int canvas_width, int canvas_height) {
 | 
			
		||||
 | 
			
		||||
    m_moves_slider->render(canvas_width, canvas_height);
 | 
			
		||||
    m_layers_slider->render(canvas_width, canvas_height);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -809,6 +809,8 @@ public:
 | 
			
		|||
    void reset_shell();
 | 
			
		||||
    void load_shells(const Print& print, bool initialized, bool force_previewing = false);
 | 
			
		||||
    void set_shells_on_preview(bool is_previewing) { m_shells.previewing = is_previewing; }
 | 
			
		||||
    //BBS: add all plates filament statistics
 | 
			
		||||
    void render_all_plates_stats(const std::vector<const GCodeProcessorResult*>& gcode_result_list, bool show = true) const;
 | 
			
		||||
    //BBS: GUI refactor: add canvas width and height
 | 
			
		||||
    void render(int canvas_width, int canvas_height, int right_margin);
 | 
			
		||||
    //BBS
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -74,8 +74,8 @@ static constexpr const float TRACKBALLSIZE = 0.8f;
 | 
			
		|||
 | 
			
		||||
static const float SLIDER_DEFAULT_RIGHT_MARGIN  = 10.0f;
 | 
			
		||||
static const float SLIDER_DEFAULT_BOTTOM_MARGIN = 10.0f;
 | 
			
		||||
static const float SLIDER_RIGHT_MARGIN          = 115.0f;
 | 
			
		||||
static const float SLIDER_BOTTOM_MARGIN         = 90.0f;
 | 
			
		||||
static const float SLIDER_RIGHT_MARGIN          = 124.0f;
 | 
			
		||||
static const float SLIDER_BOTTOM_MARGIN         = 64.0f;
 | 
			
		||||
 | 
			
		||||
float GLCanvas3D::DEFAULT_BG_LIGHT_COLOR[3] = { 0.906f, 0.906f, 0.906f };
 | 
			
		||||
float GLCanvas3D::DEFAULT_BG_LIGHT_COLOR_DARK[3] = { 0.329f, 0.329f, 0.353f };
 | 
			
		||||
| 
						 | 
				
			
			@ -1160,6 +1160,7 @@ GLCanvas3D::~GLCanvas3D()
 | 
			
		|||
    reset_volumes();
 | 
			
		||||
 | 
			
		||||
    m_sel_plate_toolbar.del_all_item();
 | 
			
		||||
    m_sel_plate_toolbar.del_stats_item();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::post_event(wxEvent &&event)
 | 
			
		||||
| 
						 | 
				
			
			@ -1608,6 +1609,11 @@ void GLCanvas3D::enable_main_toolbar(bool enable)
 | 
			
		|||
    m_main_toolbar.set_enabled(enable);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::reset_select_plate_toolbar_selection() {
 | 
			
		||||
    if (m_sel_plate_toolbar.m_all_plates_stats_item)
 | 
			
		||||
        m_sel_plate_toolbar.m_all_plates_stats_item->selected = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::enable_select_plate_toolbar(bool enable)
 | 
			
		||||
{
 | 
			
		||||
    m_sel_plate_toolbar.set_enabled(enable);
 | 
			
		||||
| 
						 | 
				
			
			@ -1721,6 +1727,9 @@ float GLCanvas3D::get_collapse_toolbar_height()
 | 
			
		|||
    return collapse_toolbar.is_enabled() ? collapse_toolbar.get_height() : 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool GLCanvas3D::make_current_for_postinit() {
 | 
			
		||||
    return _set_current();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::render(bool only_init)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -1844,7 +1853,7 @@ void GLCanvas3D::render(bool only_init)
 | 
			
		|||
            _render_platelist(!camera.is_looking_downward(), only_current, only_body, hover_id);
 | 
			
		||||
    }
 | 
			
		||||
    /* preview render */
 | 
			
		||||
    else if (m_canvas_type == ECanvasType::CanvasPreview) {
 | 
			
		||||
    else if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview) {
 | 
			
		||||
        //BBS: add outline logic
 | 
			
		||||
        _render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
 | 
			
		||||
        //BBS: GUI refactor: add canvas size as parameters
 | 
			
		||||
| 
						 | 
				
			
			@ -2045,6 +2054,17 @@ void GLCanvas3D::deselect_all()
 | 
			
		|||
    post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::set_selected_visible(bool visible)
 | 
			
		||||
{
 | 
			
		||||
    for (unsigned int i : m_selection.get_volume_idxs()) {
 | 
			
		||||
        GLVolume* volume = const_cast<GLVolume*>(m_selection.get_volume(i));
 | 
			
		||||
        volume->visible = visible;
 | 
			
		||||
        volume->color[3] = visible ? 1.f : GLVolume::MODEL_HIDDEN_COL[3];
 | 
			
		||||
        volume->render_color[3] = volume->color[3];
 | 
			
		||||
        volume->force_native_color = !visible;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::delete_selected()
 | 
			
		||||
{
 | 
			
		||||
    m_selection.erase();
 | 
			
		||||
| 
						 | 
				
			
			@ -2383,8 +2403,21 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
        for (int temp_idx = vol_idx; temp_idx < m_volumes.volumes.size() && !update_object_list; temp_idx++) {
 | 
			
		||||
            if (!m_volumes.volumes[temp_idx]->is_wipe_tower)
 | 
			
		||||
            // Volumes in m_volumes might not exist anymore, so we cannot
 | 
			
		||||
            // directly check if they are is_wipe_towers, for which we do
 | 
			
		||||
            // not want to update the object list.  Instead, we do a kind of
 | 
			
		||||
            // slow thing of seeing if they were in the deleted list, and if
 | 
			
		||||
            // so, if they were a wipe tower.
 | 
			
		||||
            bool was_deleted_wipe_tower = false;
 | 
			
		||||
            for (int del_idx = 0; del_idx < deleted_wipe_towers.size(); del_idx++) {
 | 
			
		||||
                if (deleted_wipe_towers[del_idx].volume_idx == temp_idx) {
 | 
			
		||||
                    was_deleted_wipe_tower = true;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (!was_deleted_wipe_tower) {
 | 
			
		||||
                update_object_list = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        for (int temp_idx = vol_idx; temp_idx < glvolumes_new.size() && !update_object_list; temp_idx++) {
 | 
			
		||||
            if (!glvolumes_new[temp_idx]->is_wipe_tower)
 | 
			
		||||
| 
						 | 
				
			
			@ -2536,8 +2569,12 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
 | 
			
		|||
        auto timelapse_type = dconfig.option<ConfigOptionEnum<TimelapseType>>("timelapse_type");
 | 
			
		||||
        bool timelapse_enabled = timelapse_type ? (timelapse_type->value == TimelapseType::tlSmooth) : false;
 | 
			
		||||
 | 
			
		||||
        if ((timelapse_enabled && wt) || (filaments_count > 1 && wt && co != nullptr && co->value != PrintSequence::ByObject)) {
 | 
			
		||||
        if (wt && (timelapse_enabled || filaments_count > 1)) {
 | 
			
		||||
            for (int plate_id = 0; plate_id < n_plates; plate_id++) {
 | 
			
		||||
                // If print ByObject and there is only one object in the plate, the wipe tower is allowed to be generated.
 | 
			
		||||
                if (co != nullptr && co->value == PrintSequence::ByObject && ppl.get_plate(plate_id)->printable_instance_size() != 1)
 | 
			
		||||
                    continue;
 | 
			
		||||
 | 
			
		||||
                DynamicPrintConfig& proj_cfg = wxGetApp().preset_bundle->project_config;
 | 
			
		||||
                float x = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_x"))->get_at(plate_id);
 | 
			
		||||
                float y = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_y"))->get_at(plate_id);
 | 
			
		||||
| 
						 | 
				
			
			@ -3458,6 +3495,7 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
 | 
			
		|||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (keyCode != WXK_TAB
 | 
			
		||||
| 
						 | 
				
			
			@ -3529,6 +3567,28 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
 | 
			
		|||
    if (m_gizmos.on_mouse_wheel(evt))
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    if (m_canvas_type == CanvasAssembleView && (evt.AltDown() || evt.CmdDown())) {
 | 
			
		||||
        float rotation = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta();
 | 
			
		||||
        if (evt.AltDown()) {
 | 
			
		||||
            auto clp_dist = m_gizmos.m_assemble_view_data->model_objects_clipper()->get_position();
 | 
			
		||||
            clp_dist = rotation < 0.f
 | 
			
		||||
                ? std::max(0., clp_dist - 0.01)
 | 
			
		||||
                : std::min(1., clp_dist + 0.01);
 | 
			
		||||
            m_gizmos.m_assemble_view_data->model_objects_clipper()->set_position(clp_dist, true);
 | 
			
		||||
        }
 | 
			
		||||
        else if (evt.CmdDown()) {
 | 
			
		||||
            m_explosion_ratio = rotation < 0.f
 | 
			
		||||
                ? std::max(1., m_explosion_ratio - 0.01)
 | 
			
		||||
                : std::min(3., m_explosion_ratio + 0.01);
 | 
			
		||||
            if (m_explosion_ratio != GLVolume::explosion_ratio) {
 | 
			
		||||
                for (GLVolume* volume : m_volumes.volumes) {
 | 
			
		||||
                    volume->set_bounding_boxes_as_dirty();
 | 
			
		||||
                }
 | 
			
		||||
                GLVolume::explosion_ratio = m_explosion_ratio;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    // Calculate the zoom delta and apply it to the current zoom factor
 | 
			
		||||
#ifdef SUPPORT_REVERSE_MOUSE_ZOOM
 | 
			
		||||
    double direction_factor = (wxGetApp().app_config->get("reverse_mouse_wheel_zoom") == "1") ? -1.0 : 1.0;
 | 
			
		||||
| 
						 | 
				
			
			@ -4235,6 +4295,10 @@ void GLCanvas3D::on_paint(wxPaintEvent& evt)
 | 
			
		|||
        this->render();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::force_set_focus() {
 | 
			
		||||
    m_canvas->SetFocus();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::on_set_focus(wxFocusEvent& evt)
 | 
			
		||||
{
 | 
			
		||||
    m_tooltip_enabled = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -5753,6 +5817,9 @@ bool GLCanvas3D::_init_toolbars()
 | 
			
		|||
    if (!_init_separator_toolbar())
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
    if (!_init_select_plate_toolbar())
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
#if 0
 | 
			
		||||
    if (!_init_view_toolbar())
 | 
			
		||||
        return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -5918,7 +5985,24 @@ bool GLCanvas3D::_init_main_toolbar()
 | 
			
		|||
//BBS: GUI refactor: GLToolbar
 | 
			
		||||
bool GLCanvas3D::_init_select_plate_toolbar()
 | 
			
		||||
{
 | 
			
		||||
    return true;
 | 
			
		||||
    std::string path = resources_dir() + "/images/";
 | 
			
		||||
    IMToolbarItem* item = new IMToolbarItem();
 | 
			
		||||
    bool result = item->image_texture.load_from_svg_file(path + "im_all_plates_stats.svg", false, false, false, 128);
 | 
			
		||||
    result = result && item->image_texture_transparent.load_from_svg_file(path + "im_all_plates_stats_transparent.svg", false, false, false, 128);
 | 
			
		||||
    m_sel_plate_toolbar.m_all_plates_stats_item = item;
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLCanvas3D::_update_select_plate_toolbar_stats_item(bool force_selected) {
 | 
			
		||||
    PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list();
 | 
			
		||||
    if (plate_list.get_nonempty_plate_list().size() > 1)
 | 
			
		||||
        m_sel_plate_toolbar.show_stats_item = true;
 | 
			
		||||
    else
 | 
			
		||||
        m_sel_plate_toolbar.show_stats_item = false;
 | 
			
		||||
 | 
			
		||||
    if (force_selected && m_sel_plate_toolbar.show_stats_item)
 | 
			
		||||
        m_sel_plate_toolbar.m_all_plates_stats_item->selected = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool GLCanvas3D::_update_imgui_select_plate_toolbar()
 | 
			
		||||
| 
						 | 
				
			
			@ -5926,6 +6010,8 @@ bool GLCanvas3D::_update_imgui_select_plate_toolbar()
 | 
			
		|||
    bool result = true;
 | 
			
		||||
    if (!m_sel_plate_toolbar.is_enabled()) return false;
 | 
			
		||||
 | 
			
		||||
    _update_select_plate_toolbar_stats_item();
 | 
			
		||||
 | 
			
		||||
    m_sel_plate_toolbar.del_all_item();
 | 
			
		||||
 | 
			
		||||
    PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list();
 | 
			
		||||
| 
						 | 
				
			
			@ -5941,6 +6027,7 @@ bool GLCanvas3D::_update_imgui_select_plate_toolbar()
 | 
			
		|||
        }
 | 
			
		||||
        m_sel_plate_toolbar.m_items.push_back(item);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    m_sel_plate_toolbar.is_display_scrollbar = false;
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -6565,7 +6652,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with
 | 
			
		|||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
                }, with_outline);
 | 
			
		||||
            if (m_canvas_type == CanvasAssembleView) {
 | 
			
		||||
            if (m_canvas_type == CanvasAssembleView && m_gizmos.m_assemble_view_data->model_objects_clipper()->get_position() > 0) {
 | 
			
		||||
                const GLGizmosManager& gm = get_gizmos_manager();
 | 
			
		||||
                shader->stop_using();
 | 
			
		||||
                gm.render_painter_assemble_view();
 | 
			
		||||
| 
						 | 
				
			
			@ -7031,20 +7118,26 @@ void GLCanvas3D::_render_main_toolbar()
 | 
			
		|||
 | 
			
		||||
//BBS: GUI refactor: GLToolbar adjust
 | 
			
		||||
//when rendering, {0, 0} is at the center, {-0.5, 0.5} at the left-up
 | 
			
		||||
void GLCanvas3D::_render_imgui_select_plate_toolbar() const
 | 
			
		||||
void GLCanvas3D::_render_imgui_select_plate_toolbar()
 | 
			
		||||
{
 | 
			
		||||
    if (!m_sel_plate_toolbar.is_enabled())
 | 
			
		||||
    if (!m_sel_plate_toolbar.is_enabled()) {
 | 
			
		||||
        if (!m_render_preview)
 | 
			
		||||
            m_render_preview = true;
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    IMToolbarItem* all_plates_stats_item = m_sel_plate_toolbar.m_all_plates_stats_item;
 | 
			
		||||
 | 
			
		||||
    PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list();
 | 
			
		||||
    for (int i = 0; i < plate_list.get_plate_count(); i++) {
 | 
			
		||||
        if (i < m_sel_plate_toolbar.m_items.size()) {
 | 
			
		||||
            if (i == plate_list.get_curr_plate_index())
 | 
			
		||||
            if (i == plate_list.get_curr_plate_index() && !all_plates_stats_item->selected)
 | 
			
		||||
                m_sel_plate_toolbar.m_items[i]->selected = true;
 | 
			
		||||
            else
 | 
			
		||||
                m_sel_plate_toolbar.m_items[i]->selected = false;
 | 
			
		||||
 | 
			
		||||
            m_sel_plate_toolbar.m_items[i]->percent = plate_list.get_plate(i)->get_slicing_percent();
 | 
			
		||||
 | 
			
		||||
            if (plate_list.get_plate(i)->is_slice_result_valid()) {
 | 
			
		||||
                if (plate_list.get_plate(i)->is_slice_result_ready_for_print())
 | 
			
		||||
                    m_sel_plate_toolbar.m_items[i]->slice_state = IMToolbarItem::SliceState::SLICED;
 | 
			
		||||
| 
						 | 
				
			
			@ -7058,6 +7151,46 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar() const
 | 
			
		|||
                m_sel_plate_toolbar.m_items[i]->slice_state = IMToolbarItem::SliceState::SLICING;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (m_sel_plate_toolbar.show_stats_item) {
 | 
			
		||||
        all_plates_stats_item->percent = 0.0f;
 | 
			
		||||
 | 
			
		||||
        size_t sliced_plates_cnt = 0;
 | 
			
		||||
        bool slice_failed = false;
 | 
			
		||||
        for (auto plate : plate_list.get_nonempty_plate_list()) {
 | 
			
		||||
            if (plate->is_slice_result_valid() && plate->is_slice_result_ready_for_print())
 | 
			
		||||
                sliced_plates_cnt++;
 | 
			
		||||
            if (plate->is_slice_result_valid() && !plate->is_slice_result_ready_for_print())
 | 
			
		||||
                slice_failed = true;
 | 
			
		||||
        }
 | 
			
		||||
        all_plates_stats_item->percent = (float)(sliced_plates_cnt) / (float)(plate_list.get_nonempty_plate_list().size()) * 100.0f;
 | 
			
		||||
 | 
			
		||||
        if (all_plates_stats_item->percent == 0.0f)
 | 
			
		||||
            all_plates_stats_item->slice_state = IMToolbarItem::SliceState::UNSLICED;
 | 
			
		||||
        else if (sliced_plates_cnt == plate_list.get_nonempty_plate_list().size())
 | 
			
		||||
            all_plates_stats_item->slice_state = IMToolbarItem::SliceState::SLICED;
 | 
			
		||||
        else if (all_plates_stats_item->percent < 100.0f)
 | 
			
		||||
            all_plates_stats_item->slice_state = IMToolbarItem::SliceState::SLICING;
 | 
			
		||||
 | 
			
		||||
        if (slice_failed)
 | 
			
		||||
            all_plates_stats_item->slice_state = IMToolbarItem::SliceState::SLICE_FAILED;
 | 
			
		||||
 | 
			
		||||
        // Changing parameters does not invalid all plates, need extra logic to validate
 | 
			
		||||
        bool gcode_result_valid = true;
 | 
			
		||||
        for (auto gcode_result : plate_list.get_nonempty_plates_slice_results()) {
 | 
			
		||||
            if (gcode_result->moves.size() == 0) {
 | 
			
		||||
                gcode_result_valid = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (all_plates_stats_item->selected && all_plates_stats_item->slice_state == IMToolbarItem::SliceState::SLICED && gcode_result_valid) {
 | 
			
		||||
            m_gcode_viewer.render_all_plates_stats(plate_list.get_nonempty_plates_slice_results());
 | 
			
		||||
            m_render_preview = false;
 | 
			
		||||
        }
 | 
			
		||||
        else{
 | 
			
		||||
            m_gcode_viewer.render_all_plates_stats(plate_list.get_nonempty_plates_slice_results(), false);
 | 
			
		||||
            m_render_preview = true;
 | 
			
		||||
        }
 | 
			
		||||
    }else
 | 
			
		||||
        m_render_preview = true;
 | 
			
		||||
 | 
			
		||||
    // places the toolbar on the top_left corner of the 3d scene
 | 
			
		||||
#if ENABLE_RETINA_GL
 | 
			
		||||
| 
						 | 
				
			
			@ -7081,7 +7214,7 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar() const
 | 
			
		|||
    float button_margin = frame_padding;
 | 
			
		||||
 | 
			
		||||
    ImGuiWrapper& imgui = *wxGetApp().imgui();
 | 
			
		||||
    int item_count = m_sel_plate_toolbar.m_items.size();
 | 
			
		||||
    int item_count = m_sel_plate_toolbar.m_items.size() + (m_sel_plate_toolbar.show_stats_item ? 1 : 0);
 | 
			
		||||
    bool show_scroll = item_count * (button_height + frame_padding * 2.0f + button_margin) - button_margin + 22.0f * f_scale > canvas_h ? true: false;
 | 
			
		||||
    show_scroll = m_sel_plate_toolbar.is_display_scrollbar && show_scroll;
 | 
			
		||||
    float window_height = std::min(item_count * (button_height + (frame_padding + margin_size) * 2.0f + button_margin) - button_margin + 28.0f * f_scale, canvas_h);
 | 
			
		||||
| 
						 | 
				
			
			@ -7122,7 +7255,89 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar() const
 | 
			
		|||
    ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);               // No tint
 | 
			
		||||
    ImVec2 margin = ImVec2(margin_size, margin_size);
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < item_count; i++) {
 | 
			
		||||
    if(m_sel_plate_toolbar.show_stats_item)
 | 
			
		||||
    {
 | 
			
		||||
        // draw image
 | 
			
		||||
        ImVec2 button_start_pos = ImGui::GetCursorScreenPos();
 | 
			
		||||
 | 
			
		||||
        if (all_plates_stats_item->selected) {
 | 
			
		||||
            ImGui::PushStyleColor(ImGuiCol_Button, button_active);
 | 
			
		||||
            ImGui::PushStyleColor(ImGuiCol_ButtonHovered, button_active);
 | 
			
		||||
            ImGui::PushStyleColor(ImGuiCol_ButtonActive, button_active);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(128.0f, 128.0f, 128.0f, 0.0f));
 | 
			
		||||
            if (all_plates_stats_item->slice_state == IMToolbarItem::SliceState::SLICE_FAILED) {
 | 
			
		||||
                ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetStyleColorVec4(ImGuiCol_Button));
 | 
			
		||||
                ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_Button));
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                ImGui::PushStyleColor(ImGuiCol_ButtonHovered, button_hover);
 | 
			
		||||
                ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.42f, 0.42f, 0.42f, 1.0f));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ImVec4 text_clr;
 | 
			
		||||
        ImTextureID btn_texture_id;
 | 
			
		||||
        if (all_plates_stats_item->slice_state == IMToolbarItem::SliceState::UNSLICED || all_plates_stats_item->slice_state == IMToolbarItem::SliceState::SLICING || all_plates_stats_item->slice_state == IMToolbarItem::SliceState::SLICE_FAILED)
 | 
			
		||||
        {
 | 
			
		||||
            text_clr = ImVec4(0, 174.0f / 255.0f, 66.0f / 255.0f, 0.2f);
 | 
			
		||||
            btn_texture_id = (ImTextureID)(intptr_t)(all_plates_stats_item->image_texture_transparent.get_id());
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            text_clr = ImVec4(0, 174.0f / 255.0f, 66.0f / 255.0f, 1);
 | 
			
		||||
            btn_texture_id = (ImTextureID)(intptr_t)(all_plates_stats_item->image_texture.get_id());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (ImGui::ImageButton2(btn_texture_id, size, {0,0}, {1,1}, frame_padding, bg_col, tint_col, margin)) {
 | 
			
		||||
            if (all_plates_stats_item->slice_state != IMToolbarItem::SliceState::SLICE_FAILED) {
 | 
			
		||||
                if (m_process && !m_process->running()) {
 | 
			
		||||
                    for (int i = 0; i < m_sel_plate_toolbar.m_items.size(); i++) {
 | 
			
		||||
                        m_sel_plate_toolbar.m_items[i]->selected = false;
 | 
			
		||||
                    }
 | 
			
		||||
                    all_plates_stats_item->selected = true;
 | 
			
		||||
                    wxCommandEvent evt = wxCommandEvent(EVT_GLTOOLBAR_SLICE_ALL);
 | 
			
		||||
                    wxPostEvent(wxGetApp().plater(), evt);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ImGui::PopStyleColor(3);
 | 
			
		||||
 | 
			
		||||
        ImVec2 start_pos = ImVec2(button_start_pos.x + frame_padding + margin.x, button_start_pos.y + frame_padding + margin.y);
 | 
			
		||||
        if (all_plates_stats_item->slice_state == IMToolbarItem::SliceState::UNSLICED) {
 | 
			
		||||
            ImVec2 size = ImVec2(button_width, button_height);
 | 
			
		||||
            ImVec2 end_pos = ImVec2(start_pos.x + size.x, start_pos.y + size.y);
 | 
			
		||||
            ImGui::GetForegroundDrawList()->AddRectFilled(start_pos, end_pos, IM_COL32(0, 0, 0, 80));
 | 
			
		||||
        }
 | 
			
		||||
        else if (all_plates_stats_item->slice_state == IMToolbarItem::SliceState::SLICING) {
 | 
			
		||||
            ImVec2 size = ImVec2(button_width, button_height * all_plates_stats_item->percent / 100.0f);
 | 
			
		||||
            ImVec2 rect_start_pos = ImVec2(start_pos.x, start_pos.y + size.y);
 | 
			
		||||
            ImVec2 rect_end_pos = ImVec2(start_pos.x + button_width, start_pos.y + button_height);
 | 
			
		||||
            ImGui::GetForegroundDrawList()->AddRectFilled(rect_start_pos, rect_end_pos, IM_COL32(0, 0, 0, 80));
 | 
			
		||||
        }
 | 
			
		||||
        else if (all_plates_stats_item->slice_state == IMToolbarItem::SliceState::SLICE_FAILED) {
 | 
			
		||||
            ImVec2 size = ImVec2(button_width, button_height);
 | 
			
		||||
            ImVec2 end_pos = ImVec2(start_pos.x + size.x, start_pos.y + size.y);
 | 
			
		||||
            ImGui::GetForegroundDrawList()->AddRectFilled(start_pos, end_pos, IM_COL32(40, 1, 1, 64));
 | 
			
		||||
            ImGui::GetForegroundDrawList()->AddRect(start_pos, end_pos, IM_COL32(208, 27, 27, 255), 0.0f, 0, 1.0f);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // draw text
 | 
			
		||||
        GImGui->FontSize = 15.0f;
 | 
			
		||||
        ImGui::PushStyleColor(ImGuiCol_Text, text_clr);
 | 
			
		||||
        ImVec2 text_size = ImGui::CalcTextSize(("All Plates"));
 | 
			
		||||
        ImVec2 text_start_pos = ImVec2(start_pos.x + (button_width - text_size.x) / 2, start_pos.y + 3.0f * button_height / 5.0f);
 | 
			
		||||
        ImGui::RenderText(text_start_pos, ("All Plates"));
 | 
			
		||||
        text_size = ImGui::CalcTextSize(("Stats"));
 | 
			
		||||
        text_start_pos = ImVec2(start_pos.x + (button_width - text_size.x) / 2, text_start_pos.y + ImGui::GetTextLineHeight());
 | 
			
		||||
        ImGui::RenderText(text_start_pos, ("Stats"));
 | 
			
		||||
        ImGui::PopStyleColor();
 | 
			
		||||
        ImGui::SetWindowFontScale(1.2f);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < m_sel_plate_toolbar.m_items.size(); i++) {
 | 
			
		||||
        IMToolbarItem* item = m_sel_plate_toolbar.m_items[i];
 | 
			
		||||
 | 
			
		||||
        // draw image
 | 
			
		||||
| 
						 | 
				
			
			@ -7134,11 +7349,16 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar() const
 | 
			
		|||
        if (item->selected) {
 | 
			
		||||
            ImGui::PushStyleColor(ImGuiCol_Button, button_active);
 | 
			
		||||
            ImGui::PushStyleColor(ImGuiCol_ButtonHovered, button_active);
 | 
			
		||||
        } else
 | 
			
		||||
        } 
 | 
			
		||||
        else {
 | 
			
		||||
            ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(128.0f, 128.0f, 128.0f, 0.0f));
 | 
			
		||||
            ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.42f, 0.42f, 0.42f, 1.0f));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (ImGui::ImageButton2(item->texture_id, size, uv0, uv1, frame_padding, bg_col, tint_col, margin)) {
 | 
			
		||||
            if (m_process && !m_process->running()) {
 | 
			
		||||
                all_plates_stats_item->selected = false;
 | 
			
		||||
                item->selected = true;
 | 
			
		||||
                // begin to slicing plate
 | 
			
		||||
                wxCommandEvent* evt = new wxCommandEvent(EVT_GLTOOLBAR_SELECT_SLICED_PLATE);
 | 
			
		||||
                evt->SetInt(i);
 | 
			
		||||
| 
						 | 
				
			
			@ -7146,10 +7366,7 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar() const
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (item->selected)
 | 
			
		||||
            ImGui::PopStyleColor(2);
 | 
			
		||||
        else
 | 
			
		||||
            ImGui::PopStyleColor(1);
 | 
			
		||||
        ImGui::PopStyleColor(2);
 | 
			
		||||
 | 
			
		||||
        ImVec2 start_pos = ImVec2(button_start_pos.x + frame_padding + margin.x, button_start_pos.y + frame_padding + margin.y);
 | 
			
		||||
        if (item->slice_state == IMToolbarItem::SliceState::UNSLICED) {
 | 
			
		||||
| 
						 | 
				
			
			@ -7341,60 +7558,59 @@ void GLCanvas3D::_render_paint_toolbar() const
 | 
			
		|||
    float f_scale = 1.0;
 | 
			
		||||
#endif
 | 
			
		||||
    std::vector<std::string> colors = wxGetApp().plater()->get_extruder_colors_from_plater_config();
 | 
			
		||||
    ImGuiWrapper& imgui = *wxGetApp().imgui();
 | 
			
		||||
    auto canvas_w = float(get_canvas_size().get_width());
 | 
			
		||||
    int extruder_num = colors.size();
 | 
			
		||||
 | 
			
		||||
    std::vector<std::string> filament_text_first_line;
 | 
			
		||||
    std::vector<std::string> filament_text_second_line;
 | 
			
		||||
    {
 | 
			
		||||
        auto preset_bundle = wxGetApp().preset_bundle;
 | 
			
		||||
        for (auto filament_name : preset_bundle->filament_presets) {
 | 
			
		||||
            for (auto iter = preset_bundle->filaments.lbegin(); iter != preset_bundle->filaments.end(); iter++) {
 | 
			
		||||
                if (filament_name.compare(iter->name) == 0) {
 | 
			
		||||
                    std::string display_filament_type;
 | 
			
		||||
                    iter->config.get_filament_type(display_filament_type);
 | 
			
		||||
                    auto pos = display_filament_type.find(' ');
 | 
			
		||||
                    if (pos != std::string::npos) {
 | 
			
		||||
                        filament_text_first_line.push_back(display_filament_type.substr(0, pos));
 | 
			
		||||
                        filament_text_second_line.push_back(display_filament_type.substr(pos + 1));
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        filament_text_first_line.push_back(display_filament_type);
 | 
			
		||||
                        filament_text_second_line.push_back("");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ImGuiWrapper& imgui = *wxGetApp().imgui();
 | 
			
		||||
    float canvas_w = float(get_canvas_size().get_width());
 | 
			
		||||
    int item_spacing = 8 * wxGetApp().toolbar_icon_scale() * f_scale;
 | 
			
		||||
    float button_size = GLToolbar::Default_Icons_Size * f_scale * wxGetApp().toolbar_icon_scale() + item_spacing;
 | 
			
		||||
 | 
			
		||||
    std::vector<std::string> filament_types;
 | 
			
		||||
   {
 | 
			
		||||
       auto preset_bundle = wxGetApp().preset_bundle;
 | 
			
		||||
       for (auto filament_name : preset_bundle->filament_presets) {
 | 
			
		||||
           for (auto iter = preset_bundle->filaments.lbegin(); iter != preset_bundle->filaments.end(); iter++) {
 | 
			
		||||
               if (filament_name.compare(iter->name) == 0) {
 | 
			
		||||
                   std::string display_filament_type;
 | 
			
		||||
                   iter->config.get_filament_type(display_filament_type);
 | 
			
		||||
                   filament_types.push_back(display_filament_type);
 | 
			
		||||
               }
 | 
			
		||||
           }
 | 
			
		||||
       }
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
    float button_size  = GLToolbar::Default_Icons_Size * f_scale * wxGetApp().toolbar_icon_scale() + item_spacing;
 | 
			
		||||
 | 
			
		||||
    imgui.set_next_window_pos(0.5f * (canvas_w + (button_size + item_spacing) * extruder_num), button_size + item_spacing * 2, ImGuiCond_Always, 1.0f, 1.0f);
 | 
			
		||||
    imgui.set_next_window_pos(0.5f * canvas_w, 0, ImGuiCond_Always, 0.5f, 0.0f);
 | 
			
		||||
    imgui.begin(_L("Paint Toolbar"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar);
 | 
			
		||||
    bool disabled = !wxGetApp().plater()->can_fillcolor();
 | 
			
		||||
    unsigned char rgb[3];
 | 
			
		||||
    float cursor_y = ImGui::GetCursorPosY();
 | 
			
		||||
 | 
			
		||||
    float max_text = 0;
 | 
			
		||||
    for (int i = 0; i < extruder_num; i++) {
 | 
			
		||||
        std::string item_text = (boost::format("%1%%2%") % (i + 1) % filament_types[i]).str();
 | 
			
		||||
        ImVec2 label_size = ImGui::CalcTextSize(item_text.c_str(), NULL, true);
 | 
			
		||||
        if (label_size.x > button_size)
 | 
			
		||||
               label_size.x  = button_size * 0.6;
 | 
			
		||||
        max_text = std::max(max_text,label_size.x);
 | 
			
		||||
    }
 | 
			
		||||
    for (int i = 0; i < extruder_num; i++) {
 | 
			
		||||
        if (filament_types.size() <= i) continue;
 | 
			
		||||
 | 
			
		||||
        ImGui::SameLine(item_spacing / 2 + (button_size - max_text) / 2 + (button_size + item_spacing) * i);
 | 
			
		||||
        ImGui::PushID(i);
 | 
			
		||||
        if (i > 0)
 | 
			
		||||
            ImGui::SameLine(0.0f, item_spacing);
 | 
			
		||||
        Slic3r::GUI::BitmapCache::parse_color(colors[i], rgb);
 | 
			
		||||
        ImGui::PushStyleColor(ImGuiCol_Button, ImColor(rgb[0], rgb[1], rgb[2]).Value);
 | 
			
		||||
        ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImColor(rgb[0], rgb[1], rgb[2]).Value);
 | 
			
		||||
        ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImColor(rgb[0], rgb[1], rgb[2]).Value);
 | 
			
		||||
        if (disabled)
 | 
			
		||||
            ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
 | 
			
		||||
 | 
			
		||||
        if (ImGui::Button("", ImVec2(button_size, button_size))) {
 | 
			
		||||
        if (ImGui::Button(("##filament_button" + std::to_string(i)).c_str(), ImVec2(button_size, button_size))) {
 | 
			
		||||
            wxPostEvent(m_canvas, IntEvent(EVT_GLTOOLBAR_FILLCOLOR, i + 1));
 | 
			
		||||
        }
 | 
			
		||||
        if (ImGui::IsItemHovered()) {
 | 
			
		||||
            ImGui::BeginTooltip();
 | 
			
		||||
            ImGui::PushTextWrapPos(ImGui::GetFontSize() * 20.0f);
 | 
			
		||||
            ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.00f, 1.00f, 1.00f, 1.00f));
 | 
			
		||||
            ImGui::TextUnformatted(_L((boost::format("Shortcut key %1%") % (i + 1)).str()).ToUTF8().data());
 | 
			
		||||
            ImGui::TextUnformatted((boost::format(_u8L("Shortcut key %1%")) % (i + 1)).str().c_str());
 | 
			
		||||
            ImGui::PopStyleColor(1);
 | 
			
		||||
            ImGui::PopTextWrapPos();
 | 
			
		||||
            ImGui::EndTooltip();
 | 
			
		||||
| 
						 | 
				
			
			@ -7402,82 +7618,30 @@ void GLCanvas3D::_render_paint_toolbar() const
 | 
			
		|||
        ImGui::PopStyleColor(3);
 | 
			
		||||
        if (disabled)
 | 
			
		||||
            ImGui::PopItemFlag();
 | 
			
		||||
        ImGui::PopID();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    float text_offset_y = 3.0f * f_scale;
 | 
			
		||||
    for (int i = 0; i < extruder_num; i++){
 | 
			
		||||
        if (filament_types.size() <= i) continue;
 | 
			
		||||
        //TODO use filament type from filament management, current use PLA by default
 | 
			
		||||
        std::string item_text = (boost::format("%1%%2%") % (i + 1) % filament_types[i]).str();
 | 
			
		||||
        const ImVec2 label_size = ImGui::CalcTextSize(item_text.c_str(), NULL, true);
 | 
			
		||||
 | 
			
		||||
        int len = strlen(filament_types[i].c_str());
 | 
			
		||||
 | 
			
		||||
        ImGui::SameLine(item_spacing / 2 + (button_size - max_text) / 2 + (button_size + item_spacing) * i);
 | 
			
		||||
 | 
			
		||||
        int count = 0;
 | 
			
		||||
        if (label_size.x > button_size)
 | 
			
		||||
        {
 | 
			
		||||
            for (int j = 0; j < filament_types[i].size(); j++)
 | 
			
		||||
            {
 | 
			
		||||
                 if(std::isalpha(filament_types[i][j]))
 | 
			
		||||
                    count++;
 | 
			
		||||
                 else
 | 
			
		||||
                     break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (i > 8)
 | 
			
		||||
        {
 | 
			
		||||
            if (label_size.x > button_size)
 | 
			
		||||
            {
 | 
			
		||||
                if(count * ImGui::GetFontSize() > button_size){
 | 
			
		||||
                    if ((len - (count + 1)) <= 3)
 | 
			
		||||
                        item_text = "\t" + std::to_string(i + 1) + "\n" + filament_types[i].substr(0, count) + "\n" + "\t" + filament_types[i].substr(count, len);
 | 
			
		||||
                    else
 | 
			
		||||
                        item_text = "\t" + std::to_string(i + 1) + "\n" + filament_types[i].substr(0, count + 1) + "\n"+ filament_types[i].substr(count + 1, len);
 | 
			
		||||
                } else {
 | 
			
		||||
                    if (count <= 4)
 | 
			
		||||
                        item_text = "\t" + std::to_string(i + 1) + "\n" + "   " + filament_types[i].substr(0, count + 1) + "\n" + filament_types[i].substr(count + 1, len);
 | 
			
		||||
                    else
 | 
			
		||||
                        item_text = "\t" + std::to_string(i + 1) + "\n" + filament_types[i].substr(0, count + 1) + "\n" + filament_types[i].substr(count + 1, len);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                item_text = (boost::format("\t%1%\n   %2%") % (i + 1) % filament_types[i]).str();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            if (label_size.x > button_size)
 | 
			
		||||
            {
 | 
			
		||||
                if(count * ImGui::GetFontSize() > button_size){
 | 
			
		||||
                    if ((len - (count + 1)) <= 3)
 | 
			
		||||
                        item_text = "\t " + std::to_string(i + 1) + "\n" + filament_types[i].substr(0, count) + "\n" + "\t" + filament_types[i].substr(count, len);
 | 
			
		||||
                    else
 | 
			
		||||
                        item_text = "\t " + std::to_string(i + 1) + "\n" + filament_types[i].substr(0, count + 1) + "\n"+ filament_types[i].substr(count + 1, len);
 | 
			
		||||
                } else {
 | 
			
		||||
                    if (count <= 4)
 | 
			
		||||
                        item_text = "\t " + std::to_string(i + 1) + "\n" + "   " + filament_types[i].substr(0, count + 1) + "\n" + filament_types[i].substr(count + 1, len);
 | 
			
		||||
                    else
 | 
			
		||||
                        item_text = "\t " + std::to_string(i + 1) + "\n" + filament_types[i].substr(0, count + 1) + "\n" + filament_types[i].substr(count + 1, len);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
               item_text = (boost::format("\t  %1%\n\t%2%") % (i + 1) % filament_types[i]).str();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Slic3r::GUI::BitmapCache::parse_color(colors[i], rgb);
 | 
			
		||||
        float gray = 0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2];
 | 
			
		||||
        ImVec4 text_color = gray < 80 ? ImVec4(255, 255, 255, 255) : ImVec4(0, 0, 0, 255);
 | 
			
		||||
        
 | 
			
		||||
        ImVec2 number_label_size = ImGui::CalcTextSize(std::to_string(i + 1).c_str());
 | 
			
		||||
        ImGui::SetCursorPosY(cursor_y + text_offset_y);
 | 
			
		||||
        ImGui::SetCursorPosX(item_spacing + i * (item_spacing + button_size) + (button_size - number_label_size.x) / 2);
 | 
			
		||||
        ImGui::TextColored(text_color, std::to_string(i + 1).c_str());
 | 
			
		||||
 | 
			
		||||
        if (gray < 80){
 | 
			
		||||
                ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), item_text.c_str());
 | 
			
		||||
        } else{
 | 
			
		||||
                ImGui::TextColored(ImVec4(0.0f, 0.0f, 0.0f, 1.0f), item_text.c_str());
 | 
			
		||||
        }
 | 
			
		||||
        ImVec2 filament_first_line_label_size = ImGui::CalcTextSize(filament_text_first_line[i].c_str());
 | 
			
		||||
        ImGui::SetCursorPosY(cursor_y + text_offset_y + number_label_size.y);
 | 
			
		||||
        ImGui::SetCursorPosX(item_spacing + i * (item_spacing + button_size) + (button_size - filament_first_line_label_size.x) / 2);
 | 
			
		||||
        ImGui::TextColored(text_color, filament_text_first_line[i].c_str());
 | 
			
		||||
 | 
			
		||||
        ImVec2 filament_second_line_label_size = ImGui::CalcTextSize(filament_text_second_line[i].c_str());
 | 
			
		||||
        ImGui::SetCursorPosY(cursor_y + text_offset_y + number_label_size.y + filament_first_line_label_size.y);
 | 
			
		||||
        ImGui::SetCursorPosX(item_spacing + i * (item_spacing + button_size) + (button_size - filament_second_line_label_size.x) / 2);
 | 
			
		||||
        ImGui::TextColored(text_color, filament_text_second_line[i].c_str());
 | 
			
		||||
    }
 | 
			
		||||
    ImGui::AlignTextToFramePadding();
 | 
			
		||||
 | 
			
		||||
    imgui.end();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -543,6 +543,7 @@ private:
 | 
			
		|||
    bool m_dirty;
 | 
			
		||||
    bool m_initialized;
 | 
			
		||||
    //BBS: add flag to controll rendering
 | 
			
		||||
    bool m_render_preview{ true };
 | 
			
		||||
    bool m_enable_render { true };
 | 
			
		||||
    bool m_apply_zoom_to_volumes_filter;
 | 
			
		||||
    bool m_picking_enabled;
 | 
			
		||||
| 
						 | 
				
			
			@ -785,6 +786,8 @@ public:
 | 
			
		|||
    void enable_selection(bool enable);
 | 
			
		||||
    void enable_main_toolbar(bool enable);
 | 
			
		||||
    //BBS: GUI refactor: GLToolbar
 | 
			
		||||
    void _update_select_plate_toolbar_stats_item(bool force_selected = false);
 | 
			
		||||
    void reset_select_plate_toolbar_selection();
 | 
			
		||||
    void enable_select_plate_toolbar(bool enable);
 | 
			
		||||
    void enable_assemble_view_toolbar(bool enable);
 | 
			
		||||
    void enable_return_toolbar(bool enable);
 | 
			
		||||
| 
						 | 
				
			
			@ -847,6 +850,7 @@ public:
 | 
			
		|||
 | 
			
		||||
    void select_all();
 | 
			
		||||
    void deselect_all();
 | 
			
		||||
    void set_selected_visible(bool visible);
 | 
			
		||||
    void delete_selected();
 | 
			
		||||
    void ensure_on_bed(unsigned int object_idx, bool allow_negative_z);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -893,6 +897,7 @@ public:
 | 
			
		|||
    void on_gesture(wxGestureEvent& evt);
 | 
			
		||||
    void on_paint(wxPaintEvent& evt);
 | 
			
		||||
    void on_set_focus(wxFocusEvent& evt);
 | 
			
		||||
    void force_set_focus();
 | 
			
		||||
 | 
			
		||||
    Size get_canvas_size() const;
 | 
			
		||||
    Vec2d get_local_mouse_position() const;
 | 
			
		||||
| 
						 | 
				
			
			@ -1041,6 +1046,8 @@ public:
 | 
			
		|||
    // If the Z screen space coordinate is not provided, a depth buffer value is substituted.
 | 
			
		||||
    Vec3d _mouse_to_3d(const Point& mouse_pos, float* z = nullptr);
 | 
			
		||||
 | 
			
		||||
    bool make_current_for_postinit();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    bool _is_shown_on_screen() const;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1093,7 +1100,7 @@ private:
 | 
			
		|||
    void _render_current_gizmo() const;
 | 
			
		||||
    void _render_gizmos_overlay();
 | 
			
		||||
    void _render_main_toolbar();
 | 
			
		||||
    void _render_imgui_select_plate_toolbar() const;
 | 
			
		||||
    void _render_imgui_select_plate_toolbar();
 | 
			
		||||
    void _render_assemble_view_toolbar() const;
 | 
			
		||||
    void _render_return_toolbar() const;
 | 
			
		||||
    void _render_separator_toolbar_right() const;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -584,12 +584,7 @@ bool GLTexture::generate_texture_from_text(const std::string& text_str, wxFont&
 | 
			
		|||
 | 
			
		||||
    // draw message
 | 
			
		||||
    memDC.SetTextForeground(*wxWHITE);
 | 
			
		||||
 | 
			
		||||
    wxGCDC dc2(memDC);
 | 
			
		||||
    dc2.SetFont(font);
 | 
			
		||||
    dc2.SetBackground(wxBrush(background));
 | 
			
		||||
    dc2.SetTextForeground(*wxWHITE);
 | 
			
		||||
    dc2.DrawLabel(msg, wxRect(0, 0, m_width, m_height), wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
 | 
			
		||||
    memDC.DrawLabel(msg, wxRect(0, 0, m_width, m_height), wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
 | 
			
		||||
 | 
			
		||||
    memDC.SelectObject(wxNullBitmap);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,6 +39,7 @@
 | 
			
		|||
#include <wx/textctrl.h>
 | 
			
		||||
#include <wx/splash.h>
 | 
			
		||||
#include <wx/fontutil.h>
 | 
			
		||||
#include <wx/glcanvas.h>
 | 
			
		||||
 | 
			
		||||
#include "libslic3r/Utils.hpp"
 | 
			
		||||
#include "libslic3r/Model.hpp"
 | 
			
		||||
| 
						 | 
				
			
			@ -86,8 +87,8 @@
 | 
			
		|||
//BBS: DailyTip and UserGuide Dialog
 | 
			
		||||
#include "WebDownPluginDlg.hpp"
 | 
			
		||||
#include "WebGuideDialog.hpp"
 | 
			
		||||
#include "WebUserLoginDialog.hpp"
 | 
			
		||||
#include "ReleaseNote.hpp"
 | 
			
		||||
#include "PrivacyUpdateDialog.hpp"
 | 
			
		||||
#include "ModelMall.hpp"
 | 
			
		||||
 | 
			
		||||
//#ifdef WIN32
 | 
			
		||||
| 
						 | 
				
			
			@ -1016,25 +1017,31 @@ void GUI_App::post_init()
 | 
			
		|||
        mainframe->select_tab(size_t(MainFrame::tp3DEditor));
 | 
			
		||||
        plater_->select_view_3D("3D");
 | 
			
		||||
        //BBS init the opengl resource here
 | 
			
		||||
        Size canvas_size = plater_->canvas3D()->get_canvas_size();
 | 
			
		||||
        wxGetApp().imgui()->set_display_size(static_cast<float>(canvas_size.get_width()), static_cast<float>(canvas_size.get_height()));
 | 
			
		||||
        BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", start to init opengl";
 | 
			
		||||
        wxGetApp().init_opengl();
 | 
			
		||||
#ifdef __linux__
 | 
			
		||||
        if (plater_->canvas3D()->get_wxglcanvas()->IsShownOnScreen()&&plater_->canvas3D()->make_current_for_postinit()) {
 | 
			
		||||
#endif
 | 
			
		||||
            Size canvas_size = plater_->canvas3D()->get_canvas_size();
 | 
			
		||||
            wxGetApp().imgui()->set_display_size(static_cast<float>(canvas_size.get_width()), static_cast<float>(canvas_size.get_height()));
 | 
			
		||||
            BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", start to init opengl";
 | 
			
		||||
            wxGetApp().init_opengl();
 | 
			
		||||
 | 
			
		||||
        BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished init opengl";
 | 
			
		||||
        plater_->canvas3D()->init();
 | 
			
		||||
            BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished init opengl";
 | 
			
		||||
            plater_->canvas3D()->init();
 | 
			
		||||
 | 
			
		||||
        BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished init canvas3D";
 | 
			
		||||
        wxGetApp().imgui()->new_frame();
 | 
			
		||||
            BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished init canvas3D";
 | 
			
		||||
            wxGetApp().imgui()->new_frame();
 | 
			
		||||
 | 
			
		||||
        BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished init imgui frame";
 | 
			
		||||
        plater_->canvas3D()->enable_render(true);
 | 
			
		||||
            BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished init imgui frame";
 | 
			
		||||
            plater_->canvas3D()->enable_render(true);
 | 
			
		||||
 | 
			
		||||
        if (!slow_bootup) {
 | 
			
		||||
            BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", start to render a first frame for test";
 | 
			
		||||
            plater_->canvas3D()->render(false);
 | 
			
		||||
            BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished rendering a first frame for test";
 | 
			
		||||
            if (!slow_bootup) {
 | 
			
		||||
                BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", start to render a first frame for test";
 | 
			
		||||
                plater_->canvas3D()->render(false);
 | 
			
		||||
                BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished rendering a first frame for test";
 | 
			
		||||
            }
 | 
			
		||||
#ifdef __linux__
 | 
			
		||||
        }
 | 
			
		||||
#endif
 | 
			
		||||
        if (is_editor())
 | 
			
		||||
            mainframe->select_tab(size_t(0));
 | 
			
		||||
        mainframe->Thaw();
 | 
			
		||||
| 
						 | 
				
			
			@ -1119,6 +1126,9 @@ void GUI_App::post_init()
 | 
			
		|||
 | 
			
		||||
            //BBS: check new version
 | 
			
		||||
            this->check_new_version_sf();
 | 
			
		||||
			//BBS: check privacy version
 | 
			
		||||
            if (is_user_login())
 | 
			
		||||
                this->check_privacy_version(0);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1155,6 +1165,9 @@ void GUI_App::post_init()
 | 
			
		|||
    std::string functional_config_file = Slic3r::resources_dir() + "/config.json";
 | 
			
		||||
    DeviceManager::load_functional_config(encode_path(functional_config_file.c_str()));
 | 
			
		||||
 | 
			
		||||
    std::string filaments_blacklist_config_file = Slic3r::resources_dir() + "/printers/filaments_blacklist.json";
 | 
			
		||||
    DeviceManager::load_filaments_blacklist_config(encode_path(filaments_blacklist_config_file.c_str()));
 | 
			
		||||
 | 
			
		||||
    // remove old log files over LOG_FILES_MAX_NUM
 | 
			
		||||
    std::string log_addr = data_dir();
 | 
			
		||||
    if (!log_addr.empty()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1228,6 +1241,13 @@ void GUI_App::shutdown()
 | 
			
		|||
		removable_drive_manager()->shutdown();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    // destroy login dialog
 | 
			
		||||
    if (login_dlg != nullptr) {
 | 
			
		||||
        BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": destroy login dialog");
 | 
			
		||||
        delete login_dlg;
 | 
			
		||||
        login_dlg = nullptr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (m_is_recreating_gui) return;
 | 
			
		||||
    m_is_closing = true;
 | 
			
		||||
    stop_sync_user_preset();
 | 
			
		||||
| 
						 | 
				
			
			@ -1646,9 +1666,9 @@ void GUI_App::init_networking_callbacks()
 | 
			
		|||
    BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": enter, m_agent=%1%")%m_agent;
 | 
			
		||||
    if (m_agent) {
 | 
			
		||||
        //set callbacks
 | 
			
		||||
        m_agent->set_on_user_login_fn([this](int online_login, bool login) {
 | 
			
		||||
            GUI::wxGetApp().request_user_login(online_login);
 | 
			
		||||
            });
 | 
			
		||||
        //m_agent->set_on_user_login_fn([this](int online_login, bool login) {
 | 
			
		||||
        //    GUI::wxGetApp().request_user_handle(online_login);
 | 
			
		||||
        //    });
 | 
			
		||||
 | 
			
		||||
        m_agent->set_on_server_connected_fn([this]() {
 | 
			
		||||
            if (m_is_closing) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1708,6 +1728,7 @@ void GUI_App::init_networking_callbacks()
 | 
			
		|||
                                event.SetString(obj->dev_id);
 | 
			
		||||
                            } else if (state == ConnectStatus::ConnectStatusFailed) {
 | 
			
		||||
                                obj->set_access_code("");
 | 
			
		||||
                                obj->set_user_access_code("");
 | 
			
		||||
                                m_device_manager->set_selected_machine("");
 | 
			
		||||
                                wxString text;
 | 
			
		||||
                                if (msg == "5") {
 | 
			
		||||
| 
						 | 
				
			
			@ -1748,8 +1769,9 @@ void GUI_App::init_networking_callbacks()
 | 
			
		|||
                    obj->is_ams_need_update = false;
 | 
			
		||||
                    obj->parse_json(msg);
 | 
			
		||||
 | 
			
		||||
                    if (this->m_device_manager->get_selected_machine() == obj && obj->is_ams_need_update) {
 | 
			
		||||
                        GUI::wxGetApp().sidebar().load_ams_list(obj->amsList);
 | 
			
		||||
                    auto sel = this->m_device_manager->get_selected_machine();
 | 
			
		||||
                    if ((sel == obj || sel == nullptr) && obj->is_ams_need_update) {
 | 
			
		||||
                        GUI::wxGetApp().sidebar().load_ams_list(obj->dev_id, obj->amsList);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
| 
						 | 
				
			
			@ -1773,7 +1795,7 @@ void GUI_App::init_networking_callbacks()
 | 
			
		|||
                if (obj) {
 | 
			
		||||
                    obj->parse_json(msg);
 | 
			
		||||
                    if (this->m_device_manager->get_selected_machine() == obj && obj->is_ams_need_update) {
 | 
			
		||||
                        GUI::wxGetApp().sidebar().load_ams_list(obj->amsList);
 | 
			
		||||
                        GUI::wxGetApp().sidebar().load_ams_list(obj->dev_id, obj->amsList);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                });
 | 
			
		||||
| 
						 | 
				
			
			@ -1856,10 +1878,8 @@ void GUI_App::init_download_path()
 | 
			
		|||
        fs::path dp(down_path);
 | 
			
		||||
        if (!fs::exists(dp)) {
 | 
			
		||||
 | 
			
		||||
            if (!fs::create_directory(dp)) {
 | 
			
		||||
                std::string user_down_path = wxStandardPaths::Get().GetUserDir(wxStandardPaths::Dir_Downloads).ToUTF8().data();
 | 
			
		||||
                app_config->set("download_path", user_down_path);
 | 
			
		||||
            }
 | 
			
		||||
            std::string user_down_path = wxStandardPaths::Get().GetUserDir(wxStandardPaths::Dir_Downloads).ToUTF8().data();
 | 
			
		||||
            app_config->set("download_path", user_down_path);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1978,6 +1998,14 @@ void GUI_App::init_http_extra_header()
 | 
			
		|||
        m_agent->set_extra_http_header(extra_headers);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GUI_App::update_http_extra_header()
 | 
			
		||||
{
 | 
			
		||||
    std::map<std::string, std::string> extra_headers = get_extra_header();
 | 
			
		||||
    Slic3r::Http::set_extra_headers(extra_headers);
 | 
			
		||||
    if (m_agent)
 | 
			
		||||
        m_agent->set_extra_http_header(extra_headers);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string GUI_App::get_local_models_path()
 | 
			
		||||
{
 | 
			
		||||
    std::string local_path = "";
 | 
			
		||||
| 
						 | 
				
			
			@ -2064,7 +2092,7 @@ bool GUI_App::on_init_inner()
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
        for (auto d : dialogStack)
 | 
			
		||||
            d->EndModal(wxID_CANCEL);
 | 
			
		||||
            d->EndModal(wxID_ABORT);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    std::map<std::string, std::string> extra_headers = get_extra_header();
 | 
			
		||||
| 
						 | 
				
			
			@ -2123,7 +2151,7 @@ bool GUI_App::on_init_inner()
 | 
			
		|||
#endif // __APPLE__
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    bool init_dark_color_mode = app_config->get("dark_color_mode") == "1";
 | 
			
		||||
    bool init_dark_color_mode = dark_mode();
 | 
			
		||||
    bool init_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1";
 | 
			
		||||
#ifdef __WINDOWS__
 | 
			
		||||
     NppDarkMode::InitDarkMode(init_dark_color_mode, init_sys_menu_enabled);
 | 
			
		||||
| 
						 | 
				
			
			@ -2138,7 +2166,7 @@ bool GUI_App::on_init_inner()
 | 
			
		|||
 | 
			
		||||
#ifdef _MSW_DARK_MODE
 | 
			
		||||
    // app_config can be updated in check_older_app_config(), so check if dark_color_mode and sys_menu_enabled was changed
 | 
			
		||||
    if (bool new_dark_color_mode = app_config->get("dark_color_mode") == "1";
 | 
			
		||||
    if (bool new_dark_color_mode = dark_mode();
 | 
			
		||||
        init_dark_color_mode != new_dark_color_mode) {
 | 
			
		||||
 | 
			
		||||
#ifdef __WINDOWS__
 | 
			
		||||
| 
						 | 
				
			
			@ -2325,7 +2353,13 @@ bool GUI_App::on_init_inner()
 | 
			
		|||
    // Suppress the '- default -' presets.
 | 
			
		||||
    preset_bundle->set_default_suppressed(true);
 | 
			
		||||
 | 
			
		||||
    Bind(EVT_SET_SELECTED_MACHINE, &GUI_App::on_set_selected_machine, this);
 | 
			
		||||
    Bind(EVT_USER_LOGIN, &GUI_App::on_user_login, this);
 | 
			
		||||
    Bind(EVT_USER_LOGIN_HANDLE, &GUI_App::on_user_login_handle, this);
 | 
			
		||||
    Bind(EVT_CHECK_PRIVACY_VER, &GUI_App::on_check_privacy_update, this);
 | 
			
		||||
    Bind(EVT_CHECK_PRIVACY_SHOW, &GUI_App::show_check_privacy_dlg, this);
 | 
			
		||||
 | 
			
		||||
    Bind(EVT_SHOW_IP_DIALOG, &GUI_App::show_ip_address_enter_dialog_handler, this);
 | 
			
		||||
 | 
			
		||||
    Bind(EVT_SHOW_IP_DIALOG, &GUI_App::show_ip_address_enter_dialog_handler, this);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2334,6 +2368,8 @@ bool GUI_App::on_init_inner()
 | 
			
		|||
 | 
			
		||||
    if (m_agent && m_agent->is_user_login()) {
 | 
			
		||||
        enable_user_preset_folder(true);
 | 
			
		||||
    } else {
 | 
			
		||||
        enable_user_preset_folder(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // BBS if load user preset failed
 | 
			
		||||
| 
						 | 
				
			
			@ -2349,13 +2385,11 @@ bool GUI_App::on_init_inner()
 | 
			
		|||
        }
 | 
			
		||||
    //}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    if (app_config->get("sync_user_preset") == "true") {
 | 
			
		||||
        //BBS loading user preset
 | 
			
		||||
        BOOST_LOG_TRIVIAL(info) << "Loading user presets...";
 | 
			
		||||
        scrn->SetText(_L("Loading user presets..."));
 | 
			
		||||
        // Always async, not such startup step
 | 
			
		||||
        //BOOST_LOG_TRIVIAL(info) << "Loading user presets...";
 | 
			
		||||
        //scrn->SetText(_L("Loading user presets..."));
 | 
			
		||||
        if (m_agent) {
 | 
			
		||||
            start_sync_user_preset();
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -2798,6 +2832,7 @@ void GUI_App::UpdateDarkUIWin(wxWindow* win)
 | 
			
		|||
void GUI_App::Update_dark_mode_flag()
 | 
			
		||||
{
 | 
			
		||||
    m_is_dark_mode = dark_mode();
 | 
			
		||||
    BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": switch the current dark mode status to %1% ")%m_is_dark_mode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GUI_App::UpdateDlgDarkUI(wxDialog* dlg)
 | 
			
		||||
| 
						 | 
				
			
			@ -3075,6 +3110,11 @@ void GUI_App::ShowUserGuide() {
 | 
			
		|||
 | 
			
		||||
void GUI_App::ShowDownNetPluginDlg() {
 | 
			
		||||
    try {
 | 
			
		||||
        auto iter = std::find_if(dialogStack.begin(), dialogStack.end(), [](auto dialog) {
 | 
			
		||||
            return dynamic_cast<DownloadProgressDialog *>(dialog) != nullptr;
 | 
			
		||||
        });
 | 
			
		||||
        if (iter != dialogStack.end())
 | 
			
		||||
            return;
 | 
			
		||||
        DownloadProgressDialog dlg(_L("Downloading Bambu Network Plug-in"));
 | 
			
		||||
        dlg.ShowModal();
 | 
			
		||||
    } catch (std::exception &e) {
 | 
			
		||||
| 
						 | 
				
			
			@ -3082,14 +3122,24 @@ void GUI_App::ShowDownNetPluginDlg() {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GUI_App::ShowUserLogin()
 | 
			
		||||
void GUI_App::ShowUserLogin(bool show)
 | 
			
		||||
{
 | 
			
		||||
    // BBS: User Login Dialog
 | 
			
		||||
    try {
 | 
			
		||||
        ZUserLogin LoginDlg;
 | 
			
		||||
        LoginDlg.ShowModal();
 | 
			
		||||
    } catch (std::exception &e) {
 | 
			
		||||
        // wxMessageBox(e.what(), "", MB_OK);
 | 
			
		||||
    if (show) {
 | 
			
		||||
        try {
 | 
			
		||||
            if (!login_dlg)
 | 
			
		||||
                login_dlg = new ZUserLogin();
 | 
			
		||||
            else {
 | 
			
		||||
                delete login_dlg;
 | 
			
		||||
                login_dlg = new ZUserLogin();
 | 
			
		||||
            }
 | 
			
		||||
            login_dlg->ShowModal();
 | 
			
		||||
        } catch (std::exception &e) {
 | 
			
		||||
            ;
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        if (login_dlg)
 | 
			
		||||
            login_dlg->EndModal(wxID_OK);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3168,7 +3218,7 @@ void GUI_App::force_colors_update()
 | 
			
		|||
{
 | 
			
		||||
#ifdef _MSW_DARK_MODE
 | 
			
		||||
#ifdef __WINDOWS__
 | 
			
		||||
    NppDarkMode::SetDarkMode(app_config->get("dark_color_mode") == "1");
 | 
			
		||||
    NppDarkMode::SetDarkMode(dark_mode());
 | 
			
		||||
    if (WXHWND wxHWND = wxToolTip::GetToolTipCtrl())
 | 
			
		||||
        NppDarkMode::SetDarkExplorerTheme((HWND)wxHWND);
 | 
			
		||||
    NppDarkMode::SetDarkTitleBar(mainframe->GetHWND());
 | 
			
		||||
| 
						 | 
				
			
			@ -3321,6 +3371,13 @@ bool GUI_App::check_login()
 | 
			
		|||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GUI_App::request_user_handle(int online_login)
 | 
			
		||||
{
 | 
			
		||||
    auto evt = new wxCommandEvent(EVT_USER_LOGIN_HANDLE);
 | 
			
		||||
    evt->SetInt(online_login);
 | 
			
		||||
    wxQueueEvent(this, evt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GUI_App::request_user_login(int online_login)
 | 
			
		||||
{
 | 
			
		||||
    auto evt = new wxCommandEvent(EVT_USER_LOGIN);
 | 
			
		||||
| 
						 | 
				
			
			@ -3339,12 +3396,9 @@ void GUI_App::request_user_logout()
 | 
			
		|||
 | 
			
		||||
        m_agent->user_logout();
 | 
			
		||||
        m_agent->set_user_selected_machine("");
 | 
			
		||||
        BOOST_LOG_TRIVIAL(info) << "preset_folder: set to empty, user_logout";
 | 
			
		||||
        enable_user_preset_folder(false);
 | 
			
		||||
        /* delete old user settings */
 | 
			
		||||
        m_device_manager->clean_user_info();
 | 
			
		||||
        GUI::wxGetApp().sidebar().load_ams_list({});
 | 
			
		||||
        GUI::wxGetApp().remove_user_presets();
 | 
			
		||||
        GUI::wxGetApp().sidebar().load_ams_list({}, {});
 | 
			
		||||
        GUI::wxGetApp().stop_sync_user_preset();
 | 
			
		||||
 | 
			
		||||
#ifdef __WINDOWS__
 | 
			
		||||
| 
						 | 
				
			
			@ -3375,11 +3429,6 @@ std::string GUI_App::handle_web_request(std::string cmd)
 | 
			
		|||
        std::string web_cmd = j["command"].get<std::string>();
 | 
			
		||||
 | 
			
		||||
        if (web_cmd == "request_model_download") {
 | 
			
		||||
           /* json j_data = j["data"];
 | 
			
		||||
            json import_j;*/
 | 
			
		||||
            /*  import_j["model_id"] = j["data"]["model_id"].get<std::string>();
 | 
			
		||||
              import_j["profile_id"] = j["data"]["profile_id"].get<std::string>();*/
 | 
			
		||||
 | 
			
		||||
            std::string download_url = "";
 | 
			
		||||
            if (j["data"].contains("download_url"))
 | 
			
		||||
                download_url = j["data"]["download_url"].get<std::string>();
 | 
			
		||||
| 
						 | 
				
			
			@ -3575,7 +3624,7 @@ void GUI_App::request_model_download(std::string url, std::string filename)
 | 
			
		|||
    if (!check_login()) return;
 | 
			
		||||
 | 
			
		||||
    if (plater_) {
 | 
			
		||||
        plater_->request_model_download(url, filename);
 | 
			
		||||
        plater_->request_model_download();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3685,7 +3734,15 @@ void GUI_App::enable_user_preset_folder(bool enable)
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GUI_App::on_user_login(wxCommandEvent &evt)
 | 
			
		||||
void GUI_App::on_set_selected_machine(wxCommandEvent &evt)
 | 
			
		||||
{
 | 
			
		||||
    DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
 | 
			
		||||
    if (!dev || m_agent) return;
 | 
			
		||||
 | 
			
		||||
    dev->set_selected_machine(m_agent->get_user_selected_machine());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GUI_App::on_user_login_handle(wxCommandEvent &evt)
 | 
			
		||||
{
 | 
			
		||||
    if (!m_agent) { return; }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3695,8 +3752,12 @@ void GUI_App::on_user_login(wxCommandEvent &evt)
 | 
			
		|||
    // get machine list
 | 
			
		||||
    DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
 | 
			
		||||
    if (!dev) return;
 | 
			
		||||
    dev->update_user_machine_list_info();
 | 
			
		||||
    dev->set_selected_machine(m_agent->get_user_selected_machine());
 | 
			
		||||
 | 
			
		||||
    boost::thread update_thread = boost::thread([this, dev] {
 | 
			
		||||
        dev->update_user_machine_list_info();
 | 
			
		||||
        auto evt = new wxCommandEvent(EVT_SET_SELECTED_MACHINE);
 | 
			
		||||
        wxQueueEvent(this, evt);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // if (app_config->get("sync_user_preset") == "true") {
 | 
			
		||||
        enable_user_preset_folder(true);
 | 
			
		||||
| 
						 | 
				
			
			@ -3723,6 +3784,14 @@ void GUI_App::on_user_login(wxCommandEvent &evt)
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GUI_App::on_user_login(wxCommandEvent &evt)
 | 
			
		||||
{
 | 
			
		||||
    if (!m_agent) { return; }
 | 
			
		||||
    int online_login = evt.GetInt();
 | 
			
		||||
    // check privacy before handle
 | 
			
		||||
    check_privacy_version(online_login);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool GUI_App::is_studio_active()
 | 
			
		||||
{
 | 
			
		||||
    auto curr_time = std::chrono::system_clock::now();
 | 
			
		||||
| 
						 | 
				
			
			@ -3947,6 +4016,113 @@ void GUI_App::set_skip_version(bool skip)
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GUI_App::show_check_privacy_dlg(wxCommandEvent& evt)
 | 
			
		||||
{
 | 
			
		||||
    int online_login = evt.GetInt();
 | 
			
		||||
    PrivacyUpdateDialog privacy_dlg(this->mainframe, wxID_ANY, _L("Privacy Policy Update"));
 | 
			
		||||
    privacy_dlg.Bind(EVT_PRIVACY_UPDATE_CONFIRM, [this, online_login](wxCommandEvent &e) {
 | 
			
		||||
        app_config->set("privacy_version", privacy_version_info.version_str);
 | 
			
		||||
        app_config->set_bool("privacy_update_checked", true);
 | 
			
		||||
        app_config->save();
 | 
			
		||||
        request_user_handle(online_login);
 | 
			
		||||
        });
 | 
			
		||||
    privacy_dlg.Bind(EVT_PRIVACY_UPDATE_CANCEL, [this](wxCommandEvent &e) {
 | 
			
		||||
            app_config->set_bool("privacy_update_checked", false);
 | 
			
		||||
            app_config->save();
 | 
			
		||||
            if (m_agent) {
 | 
			
		||||
                m_agent->user_logout();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    privacy_dlg.set_text(privacy_version_info.description);
 | 
			
		||||
    privacy_dlg.on_show();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GUI_App::on_show_check_privacy_dlg(int online_login)
 | 
			
		||||
{
 | 
			
		||||
    auto evt = new wxCommandEvent(EVT_CHECK_PRIVACY_SHOW);
 | 
			
		||||
    evt->SetInt(online_login);
 | 
			
		||||
    wxQueueEvent(this, evt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool GUI_App::check_privacy_update()
 | 
			
		||||
{
 | 
			
		||||
    if (privacy_version_info.version_str.empty() || privacy_version_info.description.empty()
 | 
			
		||||
        || privacy_version_info.url.empty()) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string local_privacy_ver = app_config->get("privacy_version");
 | 
			
		||||
    auto curr_version = Semver::parse(local_privacy_ver);
 | 
			
		||||
    auto remote_version = Semver::parse(privacy_version_info.version_str);
 | 
			
		||||
    if (curr_version && remote_version) {
 | 
			
		||||
        if (*remote_version > *curr_version || app_config->get("privacy_update_checked") != "true") {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GUI_App::on_check_privacy_update(wxCommandEvent& evt)
 | 
			
		||||
{
 | 
			
		||||
    int online_login = evt.GetInt();
 | 
			
		||||
    bool result = check_privacy_update();
 | 
			
		||||
    if (result)
 | 
			
		||||
        on_show_check_privacy_dlg(online_login);
 | 
			
		||||
    else
 | 
			
		||||
        request_user_handle(online_login);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GUI_App::check_privacy_version(int online_login)
 | 
			
		||||
{
 | 
			
		||||
    update_http_extra_header();
 | 
			
		||||
    std::string query_params = "?policy/privacy=00.00.00.00";
 | 
			
		||||
    std::string url = get_http_url(app_config->get_country_code()) + query_params;
 | 
			
		||||
    Slic3r::Http http = Slic3r::Http::get(url);
 | 
			
		||||
 | 
			
		||||
    http.header("accept", "application/json")
 | 
			
		||||
        .timeout_connect(TIMEOUT_CONNECT)
 | 
			
		||||
        .timeout_max(TIMEOUT_RESPONSE)
 | 
			
		||||
        .on_complete([this, online_login](std::string body, unsigned) {
 | 
			
		||||
            try {
 | 
			
		||||
                json j = json::parse(body);
 | 
			
		||||
                if (j.contains("message")) {
 | 
			
		||||
                    if (j["message"].get<std::string>() == "success") {
 | 
			
		||||
                        if (j.contains("resources")) {
 | 
			
		||||
                            for (auto it = j["resources"].begin(); it != j["resources"].end(); it++) {
 | 
			
		||||
                                if (it->contains("type")) {
 | 
			
		||||
                                    if ((*it)["type"] == std::string("policy/privacy")
 | 
			
		||||
                                        && it->contains("version")
 | 
			
		||||
                                        && it->contains("description")
 | 
			
		||||
                                        && it->contains("url")
 | 
			
		||||
                                        && it->contains("force_update")) {
 | 
			
		||||
                                        privacy_version_info.version_str = (*it)["version"].get<std::string>();
 | 
			
		||||
                                        privacy_version_info.description = (*it)["description"].get<std::string>();
 | 
			
		||||
                                        privacy_version_info.url = (*it)["url"].get<std::string>();
 | 
			
		||||
                                        privacy_version_info.force_upgrade = (*it)["force_update"].get<bool>();
 | 
			
		||||
                                        break;
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            CallAfter([this, online_login]() {
 | 
			
		||||
                                auto evt = new wxCommandEvent(EVT_CHECK_PRIVACY_VER);
 | 
			
		||||
                                evt->SetInt(online_login);
 | 
			
		||||
                                wxQueueEvent(this, evt);
 | 
			
		||||
                            });
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch (...) {
 | 
			
		||||
                request_user_handle(online_login);
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        .on_error([this, online_login](std::string body, std::string error, unsigned int status) {
 | 
			
		||||
            request_user_handle(online_login);
 | 
			
		||||
            BOOST_LOG_TRIVIAL(error) << "check privacy version error" << body;
 | 
			
		||||
    }).perform();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GUI_App::no_new_version()
 | 
			
		||||
{
 | 
			
		||||
    wxCommandEvent* evt = new wxCommandEvent(EVT_SHOW_NO_NEW_VERSION);
 | 
			
		||||
| 
						 | 
				
			
			@ -3986,17 +4162,19 @@ void GUI_App::reload_settings()
 | 
			
		|||
        m_agent->get_user_presets(&user_presets);
 | 
			
		||||
        preset_bundle->load_user_presets(*app_config, user_presets, ForwardCompatibilitySubstitutionRule::Enable);
 | 
			
		||||
        preset_bundle->save_user_presets(*app_config, get_delete_cache_presets());
 | 
			
		||||
        mainframe->update_side_preset_ui();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//BBS reload when login
 | 
			
		||||
//BBS reload when logout
 | 
			
		||||
void GUI_App::remove_user_presets()
 | 
			
		||||
{
 | 
			
		||||
    if (preset_bundle && m_agent) {
 | 
			
		||||
        preset_bundle->remove_users_preset(*app_config);
 | 
			
		||||
 | 
			
		||||
        std::string user_id = m_agent->get_user_id();
 | 
			
		||||
        preset_bundle->remove_user_presets_directory(user_id);
 | 
			
		||||
        // Not remove user preset cache
 | 
			
		||||
        //std::string user_id = m_agent->get_user_id();
 | 
			
		||||
        //preset_bundle->remove_user_presets_directory(user_id);
 | 
			
		||||
 | 
			
		||||
        //update ui
 | 
			
		||||
        mainframe->update_side_preset_ui();
 | 
			
		||||
| 
						 | 
				
			
			@ -4110,9 +4288,9 @@ void GUI_App::sync_preset(Preset* preset)
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GUI_App::start_sync_user_preset(bool with_progress_dlg)
 | 
			
		||||
void GUI_App::start_sync_user_preset(bool load_immediately, bool with_progress_dlg)
 | 
			
		||||
{
 | 
			
		||||
    if (!m_agent) return;
 | 
			
		||||
    if (!m_agent || !m_agent->is_user_login()) return;
 | 
			
		||||
 | 
			
		||||
    enable_user_preset_folder(true);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -4120,32 +4298,51 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
 | 
			
		|||
    if (enable_sync)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    if (m_agent->is_user_login()) {
 | 
			
		||||
        // get setting list, update setting list
 | 
			
		||||
        std::string version = preset_bundle->get_vendor_profile_version(PresetBundle::BBL_BUNDLE).to_string();
 | 
			
		||||
        if (with_progress_dlg) {
 | 
			
		||||
            ProgressDialog dlg(_L("Loading"), "", 100, this->mainframe, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
 | 
			
		||||
            dlg.Update(0, _L("Loading user preset"));
 | 
			
		||||
            m_agent->get_setting_list(version,
 | 
			
		||||
                            [this, &dlg](int percent){
 | 
			
		||||
                                dlg.Update(percent, _L("Loading user preset"));
 | 
			
		||||
                            },
 | 
			
		||||
                            [this, &dlg]() {
 | 
			
		||||
                                dlg.GetValue();
 | 
			
		||||
                                bool cont = dlg.Update(dlg.GetValue(), _L("Loading user preset"));
 | 
			
		||||
                                return !cont;
 | 
			
		||||
                            });
 | 
			
		||||
        } else {
 | 
			
		||||
            m_agent->get_setting_list(version);
 | 
			
		||||
        }
 | 
			
		||||
        GUI::wxGetApp().reload_settings();
 | 
			
		||||
    if (load_immediately) {
 | 
			
		||||
        preset_bundle->load_user_presets(m_agent->get_user_id(), ForwardCompatibilitySubstitutionRule::Enable);
 | 
			
		||||
        mainframe->update_side_preset_ui();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ProgressFn progressFn;
 | 
			
		||||
    WasCancelledFn cancelFn;
 | 
			
		||||
    std::function<void()> finishFn;
 | 
			
		||||
 | 
			
		||||
    if (with_progress_dlg) {
 | 
			
		||||
        auto dlg = new ProgressDialog(_L("Loading"), "", 100, this->mainframe, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
 | 
			
		||||
        dlg->Update(0, _L("Loading user preset"));
 | 
			
		||||
        progressFn = [this, dlg](int percent) {
 | 
			
		||||
            CallAfter([=]{
 | 
			
		||||
                dlg->Update(percent, _L("Loading user preset"));
 | 
			
		||||
            });
 | 
			
		||||
        };
 | 
			
		||||
        cancelFn = [dlg]() {
 | 
			
		||||
            return dlg->WasCanceled();
 | 
			
		||||
        };
 | 
			
		||||
        finishFn = [this, dlg] {
 | 
			
		||||
            CallAfter([=]{
 | 
			
		||||
                dlg->Destroy();
 | 
			
		||||
                reload_settings();
 | 
			
		||||
            });
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        finishFn = [this] {
 | 
			
		||||
            CallAfter([=] {
 | 
			
		||||
                reload_settings();
 | 
			
		||||
            });
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    BOOST_LOG_TRIVIAL(info) << "start_sync_service...";
 | 
			
		||||
    //BBS
 | 
			
		||||
    enable_sync = true;
 | 
			
		||||
    m_sync_update_thread = Slic3r::create_thread(
 | 
			
		||||
        [this] {
 | 
			
		||||
        [this, progressFn, cancelFn, finishFn] {
 | 
			
		||||
            // get setting list, update setting list
 | 
			
		||||
            std::string version = preset_bundle->get_vendor_profile_version(PresetBundle::BBL_BUNDLE).to_string();
 | 
			
		||||
            m_agent->get_setting_list(version, progressFn, cancelFn);
 | 
			
		||||
            finishFn();
 | 
			
		||||
 | 
			
		||||
            int count = 0, sync_count = 0;
 | 
			
		||||
            std::vector<Preset> presets_to_sync;
 | 
			
		||||
            while (enable_sync) {
 | 
			
		||||
| 
						 | 
				
			
			@ -4182,12 +4379,13 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
 | 
			
		|||
                        unsigned int http_code = 200;
 | 
			
		||||
 | 
			
		||||
                        /* get list witch need to be deleted*/
 | 
			
		||||
                        std::vector<string>& delete_cache_presets = get_delete_cache_presets();
 | 
			
		||||
                        std::vector<string> delete_cache_presets = get_delete_cache_presets_lock();
 | 
			
		||||
                        for (auto it = delete_cache_presets.begin(); it != delete_cache_presets.end();) {
 | 
			
		||||
                            if ((*it).empty()) continue;
 | 
			
		||||
                            std::string del_setting_id = *it;
 | 
			
		||||
                            int result = m_agent->delete_setting(del_setting_id);
 | 
			
		||||
                            if (result == 0) {
 | 
			
		||||
                                preset_deleted_from_cloud(del_setting_id);
 | 
			
		||||
                                it = delete_cache_presets.erase(it);
 | 
			
		||||
                                BOOST_LOG_TRIVIAL(trace) << "sync_preset: sync operation: delete success! setting id = " << del_setting_id;
 | 
			
		||||
                            }
 | 
			
		||||
| 
						 | 
				
			
			@ -4206,6 +4404,7 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
 | 
			
		|||
 | 
			
		||||
void GUI_App::stop_sync_user_preset()
 | 
			
		||||
{
 | 
			
		||||
    remove_user_presets();
 | 
			
		||||
    enable_user_preset_folder(false);
 | 
			
		||||
 | 
			
		||||
    if (!enable_sync)
 | 
			
		||||
| 
						 | 
				
			
			@ -4216,6 +4415,16 @@ void GUI_App::stop_sync_user_preset()
 | 
			
		|||
        m_sync_update_thread.join();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GUI_App::start_http_server()
 | 
			
		||||
{
 | 
			
		||||
    if (!m_http_server.is_started())
 | 
			
		||||
        m_http_server.start();
 | 
			
		||||
}
 | 
			
		||||
void GUI_App::stop_http_server()
 | 
			
		||||
{
 | 
			
		||||
    m_http_server.stop();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool GUI_App::switch_language()
 | 
			
		||||
{
 | 
			
		||||
    if (select_language()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -4412,7 +4621,8 @@ bool GUI_App::load_language(wxString language, bool initial)
 | 
			
		|||
                        {"fr", wxString::FromUTF8("\x46\x72\x61\x6E\xC3\xA7\x61\x69\x73")},
 | 
			
		||||
                        {"it", wxString::FromUTF8("\x49\x74\x61\x6C\x69\x61\x6E\x6F")},
 | 
			
		||||
                        {"ru", wxString::FromUTF8("\xD1\x80\xD1\x83\xD1\x81\xD1\x81\xD0\xBA\xD0\xB8\xD0\xB9")},
 | 
			
		||||
                        {"hu", wxString::FromUTF8("Magyar")}
 | 
			
		||||
                        {"hu", wxString::FromUTF8("Magyar")},
 | 
			
		||||
                        {"ja", wxString::FromUTF8("\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E")}
 | 
			
		||||
                    };
 | 
			
		||||
                    for (auto l : language_descptions) {
 | 
			
		||||
                        const wxLanguageInfo *langinfo = wxLocale::FindLanguageInfo(l.first);
 | 
			
		||||
| 
						 | 
				
			
			@ -4517,7 +4727,9 @@ bool GUI_App::load_language(wxString language, bool initial)
 | 
			
		|||
            wxLANGUAGE_SPANISH,
 | 
			
		||||
            wxLANGUAGE_SWEDISH,
 | 
			
		||||
            wxLANGUAGE_DUTCH,
 | 
			
		||||
            wxLANGUAGE_HUNGARIAN};
 | 
			
		||||
            wxLANGUAGE_HUNGARIAN,
 | 
			
		||||
            wxLANGUAGE_JAPANESE
 | 
			
		||||
        };
 | 
			
		||||
        std::string cur_language = app_config->get("language");
 | 
			
		||||
        if (cur_language != "") {
 | 
			
		||||
            //cleanup the language wrongly set before
 | 
			
		||||
| 
						 | 
				
			
			@ -5027,7 +5239,12 @@ bool GUI_App::check_and_keep_current_preset_changes(const wxString& caption, con
 | 
			
		|||
                            static_cast<TabPrinter*>(tab)->cache_extruder_cnt();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    tab->cache_config_diff(selected_options);
 | 
			
		||||
                    std::vector<std::string> selected_options2;
 | 
			
		||||
                    std::transform(selected_options.begin(), selected_options.end(), std::back_inserter(selected_options2), [](auto & o) {
 | 
			
		||||
                        auto i = o.find('#');
 | 
			
		||||
                        return i != std::string::npos ? o.substr(0, i) : o;
 | 
			
		||||
                    });
 | 
			
		||||
                    tab->cache_config_diff(selected_options2);
 | 
			
		||||
                    if (!is_called_from_configwizard)
 | 
			
		||||
                        tab->m_presets->discard_current_changes();
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			@ -5114,16 +5331,31 @@ void GUI_App::load_current_presets(bool active_preset_combox/*= false*/, bool ch
 | 
			
		|||
        }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<std::string>& GUI_App::get_delete_cache_presets()
 | 
			
		||||
static std::mutex mutex_delete_cache_presets;
 | 
			
		||||
 | 
			
		||||
std::vector<std::string> & GUI_App::get_delete_cache_presets()
 | 
			
		||||
{
 | 
			
		||||
    return need_delete_presets;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<std::string> GUI_App::get_delete_cache_presets_lock()
 | 
			
		||||
{
 | 
			
		||||
    std::scoped_lock l(mutex_delete_cache_presets);
 | 
			
		||||
    return need_delete_presets;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GUI_App::delete_preset_from_cloud(std::string setting_id)
 | 
			
		||||
{
 | 
			
		||||
    std::scoped_lock l(mutex_delete_cache_presets);
 | 
			
		||||
    need_delete_presets.push_back(setting_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GUI_App::preset_deleted_from_cloud(std::string setting_id)
 | 
			
		||||
{
 | 
			
		||||
    std::scoped_lock l(mutex_delete_cache_presets);
 | 
			
		||||
    need_delete_presets.erase(std::remove(need_delete_presets.begin(), need_delete_presets.end(), setting_id), need_delete_presets.end());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool GUI_App::OnExceptionInMainLoop()
 | 
			
		||||
{
 | 
			
		||||
    generic_exception_handle();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,8 +11,10 @@
 | 
			
		|||
#include "slic3r/GUI/DeviceManager.hpp"
 | 
			
		||||
#include "slic3r/Utils/NetworkAgent.hpp"
 | 
			
		||||
#include "slic3r/GUI/WebViewDialog.hpp"
 | 
			
		||||
#include "slic3r/GUI/WebUserLoginDialog.hpp"
 | 
			
		||||
#include "slic3r/GUI/HMS.hpp"
 | 
			
		||||
#include "slic3r/GUI/Jobs/UpgradeNetworkJob.hpp"
 | 
			
		||||
#include "slic3r/GUI/HttpServer.hpp"
 | 
			
		||||
#include "../Utils/PrintHost.hpp"
 | 
			
		||||
 | 
			
		||||
#include <wx/app.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -274,7 +276,11 @@ private:
 | 
			
		|||
    bool m_networking_cancel_update { false };
 | 
			
		||||
    std::shared_ptr<UpgradeNetworkJob> m_upgrade_network_job;
 | 
			
		||||
 | 
			
		||||
    // login widget
 | 
			
		||||
    ZUserLogin*     login_dlg { nullptr };
 | 
			
		||||
 | 
			
		||||
    VersionInfo version_info;
 | 
			
		||||
    VersionInfo privacy_version_info;
 | 
			
		||||
    static std::string version_display;
 | 
			
		||||
    HMSQuery    *hms_query { nullptr };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -283,7 +289,9 @@ private:
 | 
			
		|||
    bool             m_is_dark_mode{ false };
 | 
			
		||||
    bool             m_adding_script_handler { false };
 | 
			
		||||
    bool             m_side_popup_status{false};
 | 
			
		||||
    HttpServer       m_http_server;
 | 
			
		||||
public:
 | 
			
		||||
    void            check_filaments_in_blacklist(std::string tag_supplier, std::string tag_material, bool& in_blacklist, std::string& action, std::string& info);
 | 
			
		||||
    std::string     get_local_models_path();
 | 
			
		||||
    bool            OnInit() override;
 | 
			
		||||
    bool            initialized() const { return m_initialized; }
 | 
			
		||||
| 
						 | 
				
			
			@ -380,7 +388,7 @@ public:
 | 
			
		|||
    wxString transition_tridid(int trid_id);
 | 
			
		||||
    void            ShowUserGuide();
 | 
			
		||||
    void            ShowDownNetPluginDlg();
 | 
			
		||||
    void            ShowUserLogin();
 | 
			
		||||
    void            ShowUserLogin(bool show = true);
 | 
			
		||||
    void            ShowOnlyFilament();
 | 
			
		||||
    //BBS
 | 
			
		||||
    void            request_login(bool show_user_info = false);
 | 
			
		||||
| 
						 | 
				
			
			@ -388,7 +396,8 @@ public:
 | 
			
		|||
    void            get_login_info();
 | 
			
		||||
    bool            is_user_login();
 | 
			
		||||
 | 
			
		||||
    void            request_user_login(int online_login);
 | 
			
		||||
    void            request_user_login(int online_login = 0);
 | 
			
		||||
    void            request_user_handle(int online_login = 0);
 | 
			
		||||
    void            request_user_logout();
 | 
			
		||||
    int             request_user_unbind(std::string dev_id);
 | 
			
		||||
    std::string     handle_web_request(std::string cmd);
 | 
			
		||||
| 
						 | 
				
			
			@ -401,7 +410,9 @@ public:
 | 
			
		|||
 | 
			
		||||
    void            handle_http_error(unsigned int status, std::string body);
 | 
			
		||||
    void            on_http_error(wxCommandEvent &evt);
 | 
			
		||||
    void            on_set_selected_machine(wxCommandEvent& evt);
 | 
			
		||||
    void            on_user_login(wxCommandEvent &evt);
 | 
			
		||||
    void            on_user_login_handle(wxCommandEvent& evt);
 | 
			
		||||
    void            enable_user_preset_folder(bool enable);
 | 
			
		||||
 | 
			
		||||
    // BBS
 | 
			
		||||
| 
						 | 
				
			
			@ -422,8 +433,16 @@ public:
 | 
			
		|||
    void            reload_settings();
 | 
			
		||||
    void            remove_user_presets();
 | 
			
		||||
    void            sync_preset(Preset* preset);
 | 
			
		||||
    void            start_sync_user_preset(bool with_progress_dlg = false);
 | 
			
		||||
    void            start_sync_user_preset(bool load_immediately = false, bool with_progress_dlg = false);
 | 
			
		||||
    void            stop_sync_user_preset();
 | 
			
		||||
    void            start_http_server();
 | 
			
		||||
    void            stop_http_server();
 | 
			
		||||
 | 
			
		||||
    void            on_show_check_privacy_dlg(int online_login = 0);
 | 
			
		||||
    void            show_check_privacy_dlg(wxCommandEvent& evt);
 | 
			
		||||
    void            on_check_privacy_update(wxCommandEvent &evt);
 | 
			
		||||
    bool            check_privacy_update();
 | 
			
		||||
    void            check_privacy_version(int online_login = 0);
 | 
			
		||||
 | 
			
		||||
    static bool     catch_error(std::function<void()> cb, const std::string& err);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -457,8 +476,10 @@ public:
 | 
			
		|||
    bool            checked_tab(Tab* tab);
 | 
			
		||||
    //BBS: add preset combox re-active logic
 | 
			
		||||
    void            load_current_presets(bool active_preset_combox = false, bool check_printer_presets = true);
 | 
			
		||||
    std::vector<std::string>& get_delete_cache_presets();
 | 
			
		||||
    std::vector<std::string> &get_delete_cache_presets();
 | 
			
		||||
    std::vector<std::string> get_delete_cache_presets_lock();
 | 
			
		||||
    void            delete_preset_from_cloud(std::string setting_id);
 | 
			
		||||
    void            preset_deleted_from_cloud(std::string setting_id);
 | 
			
		||||
 | 
			
		||||
    wxString        current_language_code() const { return m_wxLocale->GetCanonicalName(); }
 | 
			
		||||
	// Translate the language code to a code, for which Prusa Research maintains translations. Defaults to "en_US".
 | 
			
		||||
| 
						 | 
				
			
			@ -486,13 +507,21 @@ public:
 | 
			
		|||
    Model&      		 model();
 | 
			
		||||
    NotificationManager * notification_manager();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    std::string         m_mall_model_download_url;
 | 
			
		||||
    std::string         m_mall_model_download_name;
 | 
			
		||||
    ModelMallDialog*    m_mall_home_dialog{ nullptr };
 | 
			
		||||
    ModelMallDialog*    m_mall_publish_dialog{ nullptr };
 | 
			
		||||
 | 
			
		||||
    void            set_download_model_url(std::string url) {m_mall_model_download_url = url;}
 | 
			
		||||
    void            set_download_model_name(std::string name) {m_mall_model_download_name = name;}
 | 
			
		||||
    std::string     get_download_model_url() {return m_mall_model_download_url;}
 | 
			
		||||
    std::string     get_download_model_name() {return m_mall_model_download_name;}
 | 
			
		||||
 | 
			
		||||
    void            load_url(wxString url);
 | 
			
		||||
    void            open_mall_page_dialog();
 | 
			
		||||
    void            open_publish_page_dialog();
 | 
			
		||||
    void remove_mall_system_dialog();
 | 
			
		||||
    void            remove_mall_system_dialog();
 | 
			
		||||
    void            run_script(wxString js);
 | 
			
		||||
    bool            is_adding_script_handler() { return m_adding_script_handler; }
 | 
			
		||||
    void            set_adding_script_handler(bool status) { m_adding_script_handler = status; }
 | 
			
		||||
| 
						 | 
				
			
			@ -574,6 +603,7 @@ private:
 | 
			
		|||
    //BBS set extra header for http request
 | 
			
		||||
    std::map<std::string, std::string> get_extra_header();
 | 
			
		||||
    void            init_http_extra_header();
 | 
			
		||||
    void            update_http_extra_header();
 | 
			
		||||
    bool            check_older_app_config(Semver current_version, bool backup);
 | 
			
		||||
    void            copy_older_config();
 | 
			
		||||
    void            window_pos_save(wxTopLevelWindow* window, const std::string &name);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -432,6 +432,19 @@ std::vector<wxBitmap> MenuFactory::get_volume_bitmaps()
 | 
			
		|||
    return volume_bmps;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MenuFactory::append_menu_item_set_visible(wxMenu* menu)
 | 
			
		||||
{
 | 
			
		||||
    bool has_one_shown = false;
 | 
			
		||||
    const Selection& selection = plater()->canvas3D()->get_selection();
 | 
			
		||||
    for (unsigned int i : selection.get_volume_idxs()) {
 | 
			
		||||
        has_one_shown |= selection.get_volume(i)->visible;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    append_menu_item(menu, wxID_ANY, has_one_shown ?_L("Hide") : _L("Show"), "",
 | 
			
		||||
        [has_one_shown](wxCommandEvent&) { plater()->set_selected_visible(!has_one_shown); }, "", nullptr,
 | 
			
		||||
        []() { return true; }, m_parent);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MenuFactory::append_menu_item_delete(wxMenu* menu)
 | 
			
		||||
{
 | 
			
		||||
#ifdef __WINDOWS__
 | 
			
		||||
| 
						 | 
				
			
			@ -445,6 +458,19 @@ void MenuFactory::append_menu_item_delete(wxMenu* menu)
 | 
			
		|||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MenuFactory::append_menu_item_edit_text(wxMenu *menu)
 | 
			
		||||
{
 | 
			
		||||
#ifdef __WINDOWS__
 | 
			
		||||
    append_menu_item(
 | 
			
		||||
        menu, wxID_ANY, _L("Edit Text"), "", [](wxCommandEvent &) { plater()->edit_text(); }, "", nullptr,
 | 
			
		||||
        []() { return plater()->can_edit_text(); }, m_parent);
 | 
			
		||||
#else
 | 
			
		||||
    append_menu_item(
 | 
			
		||||
        menu, wxID_ANY, _L("Edit Text"), "", [](wxCommandEvent &) { plater()->edit_text(); }, "", nullptr,
 | 
			
		||||
        []() { return plater()->can_edit_text(); }, m_parent);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType type) {
 | 
			
		||||
    auto sub_menu = new wxMenu;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -735,6 +761,8 @@ void MenuFactory::append_menu_items_flush_options(wxMenu* menu)
 | 
			
		|||
    bool show_flush_option_menu = false;
 | 
			
		||||
    ObjectList* object_list = obj_list();
 | 
			
		||||
    const Selection& selection = get_selection();
 | 
			
		||||
    if (selection.get_object_idx() < 0)
 | 
			
		||||
        return;
 | 
			
		||||
    if (wxGetApp().plater()->get_partplate_list().get_curr_plate()->contains(selection.get_bounding_box())) {
 | 
			
		||||
        auto plate_extruders = wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_extruders();
 | 
			
		||||
        for (auto extruder : plate_extruders) {
 | 
			
		||||
| 
						 | 
				
			
			@ -784,7 +812,7 @@ void MenuFactory::append_menu_items_flush_options(wxMenu* menu)
 | 
			
		|||
    {
 | 
			
		||||
        i++;
 | 
			
		||||
        wxMenuItem* item = node->GetData();
 | 
			
		||||
        if (item->GetItemLabelText() == "Edit in Parameter Table")
 | 
			
		||||
        if (item->GetItemLabelText() == _L("Edit in Parameter Table"))
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
    menu->Insert(i, wxID_ANY, _L("Flush Options"), flush_options_menu);
 | 
			
		||||
| 
						 | 
				
			
			@ -1057,6 +1085,7 @@ void MenuFactory::create_bbl_part_menu()
 | 
			
		|||
    wxMenu* menu = &m_part_menu;
 | 
			
		||||
 | 
			
		||||
    append_menu_item_delete(menu);
 | 
			
		||||
    append_menu_item_edit_text(menu);
 | 
			
		||||
    append_menu_item_fix_through_netfabb(menu);
 | 
			
		||||
    append_menu_item_simplify(menu);
 | 
			
		||||
    append_menu_item_center(menu);
 | 
			
		||||
| 
						 | 
				
			
			@ -1310,8 +1339,9 @@ wxMenu* MenuFactory::assemble_multi_selection_menu()
 | 
			
		|||
            return nullptr;
 | 
			
		||||
 | 
			
		||||
    wxMenu* menu = new MenuWithSeparators();
 | 
			
		||||
    append_menu_item_fix_through_netfabb(menu);
 | 
			
		||||
    append_menu_item_simplify(menu);
 | 
			
		||||
    append_menu_item_set_visible(menu);
 | 
			
		||||
    //append_menu_item_fix_through_netfabb(menu);
 | 
			
		||||
    //append_menu_item_simplify(menu);
 | 
			
		||||
    append_menu_item_delete(menu);
 | 
			
		||||
    menu->AppendSeparator();
 | 
			
		||||
    append_menu_item_change_extruder(menu);
 | 
			
		||||
| 
						 | 
				
			
			@ -1328,18 +1358,36 @@ wxMenu* MenuFactory::plate_menu()
 | 
			
		|||
 | 
			
		||||
wxMenu* MenuFactory::assemble_object_menu()
 | 
			
		||||
{
 | 
			
		||||
    wxMenu* menu = new MenuWithSeparators();
 | 
			
		||||
    // Set Visible
 | 
			
		||||
    append_menu_item_set_visible(menu);
 | 
			
		||||
    // Delete
 | 
			
		||||
    append_menu_item_delete(menu);
 | 
			
		||||
    //// Object Repair
 | 
			
		||||
    //append_menu_item_fix_through_netfabb(menu);
 | 
			
		||||
    //// Object Simplify
 | 
			
		||||
    //append_menu_item_simplify(menu);
 | 
			
		||||
    menu->AppendSeparator();
 | 
			
		||||
 | 
			
		||||
    // Set filament
 | 
			
		||||
    append_menu_item_change_extruder(&m_assemble_object_menu);
 | 
			
		||||
    // Enter per object parameters
 | 
			
		||||
    append_menu_item_per_object_settings(&m_assemble_object_menu);
 | 
			
		||||
    return &m_assemble_object_menu;
 | 
			
		||||
    append_menu_item_change_extruder(menu);
 | 
			
		||||
    //// Enter per object parameters
 | 
			
		||||
    //append_menu_item_per_object_settings(menu);
 | 
			
		||||
    return menu;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
wxMenu* MenuFactory::assemble_part_menu()
 | 
			
		||||
{
 | 
			
		||||
    append_menu_item_change_extruder(&m_assemble_part_menu);
 | 
			
		||||
    append_menu_item_per_object_settings(&m_assemble_part_menu);
 | 
			
		||||
    return &m_assemble_part_menu;
 | 
			
		||||
    wxMenu* menu = new MenuWithSeparators();
 | 
			
		||||
 | 
			
		||||
    append_menu_item_set_visible(menu);
 | 
			
		||||
    append_menu_item_delete(menu);
 | 
			
		||||
    //append_menu_item_simplify(menu);
 | 
			
		||||
    menu->AppendSeparator();
 | 
			
		||||
 | 
			
		||||
    append_menu_item_change_extruder(menu);
 | 
			
		||||
    //append_menu_item_per_object_settings(menu);
 | 
			
		||||
    return menu;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MenuFactory::append_menu_item_clone(wxMenu* menu)
 | 
			
		||||
| 
						 | 
				
			
			@ -1509,7 +1557,7 @@ void MenuFactory::append_menu_item_set_printable(wxMenu* menu)
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    wxString menu_text = all_printable ? L("Set Unprintable") : _L("Set Printable");
 | 
			
		||||
    wxString menu_text = all_printable ? _L("Set Unprintable") : _L("Set Printable");
 | 
			
		||||
    append_menu_item(menu, wxID_ANY, menu_text, "", [this, all_printable](wxCommandEvent&) {
 | 
			
		||||
        Selection& selection = plater()->canvas3D()->get_selection();
 | 
			
		||||
        selection.set_printable(!all_printable);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -126,7 +126,9 @@ private:
 | 
			
		|||
    void        append_menu_item_reload_from_disk(wxMenu* menu);
 | 
			
		||||
    void        append_menu_item_replace_with_stl(wxMenu* menu);
 | 
			
		||||
    void        append_menu_item_change_extruder(wxMenu* menu);
 | 
			
		||||
    void        append_menu_item_set_visible(wxMenu* menu);
 | 
			
		||||
    void        append_menu_item_delete(wxMenu* menu);
 | 
			
		||||
    void        append_menu_item_edit_text(wxMenu *menu);
 | 
			
		||||
    void        append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu);
 | 
			
		||||
    void        append_menu_items_convert_unit(wxMenu* menu); // Add "Conver/Revert..." menu items (from/to inches/meters) after "Reload From Disk"
 | 
			
		||||
    void        append_menu_items_flush_options(wxMenu* menu);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,6 +30,7 @@
 | 
			
		|||
 | 
			
		||||
#include "slic3r/Utils/FixModelByWin10.hpp"
 | 
			
		||||
#include "libslic3r/Format/bbs_3mf.hpp"
 | 
			
		||||
#include "libslic3r/PrintConfig.hpp"
 | 
			
		||||
 | 
			
		||||
#ifdef __WXMSW__
 | 
			
		||||
#include "wx/uiaction.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -91,6 +92,9 @@ ObjectList::ObjectList(wxWindow* parent) :
 | 
			
		|||
    Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxDataViewEvent& event) {
 | 
			
		||||
        // detect the current mouse position here, to pass it to list_manipulation() method
 | 
			
		||||
        // if we detect it later, the user may have moved the mouse pointer while calculations are performed, and this would mess-up the HitTest() call performed into list_manipulation()
 | 
			
		||||
        if (!GetScreenRect().Contains(wxGetMousePosition())) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
#ifndef __WXOSX__
 | 
			
		||||
        const wxPoint mouse_pos = this->get_mouse_position_in_control();
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			@ -731,7 +735,7 @@ void ObjectList::printable_state_changed(const std::vector<ObjectVolumeID>& ov_i
 | 
			
		|||
    obj_idxs.erase(unique(obj_idxs.begin(), obj_idxs.end()), obj_idxs.end());
 | 
			
		||||
 | 
			
		||||
    // update printable state on canvas
 | 
			
		||||
    wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(obj_idxs);
 | 
			
		||||
    wxGetApp().plater()->get_view3D_canvas3D()->update_instance_printable_state_for_objects(obj_idxs);
 | 
			
		||||
 | 
			
		||||
    // update scene
 | 
			
		||||
    wxGetApp().plater()->update();
 | 
			
		||||
| 
						 | 
				
			
			@ -1397,8 +1401,21 @@ void ObjectList::key_event(wxKeyEvent& event)
 | 
			
		|||
 | 
			
		||||
void ObjectList::OnBeginDrag(wxDataViewEvent &event)
 | 
			
		||||
{
 | 
			
		||||
    bool sequential_print = (wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_enum<PrintSequence>("print_sequence") == PrintSequence::ByObject);
 | 
			
		||||
    if (!sequential_print) {
 | 
			
		||||
    int curr_obj_id = m_objects_model->GetIdByItem(event.GetItem());
 | 
			
		||||
    PartPlateList& partplate_list = wxGetApp().plater()->get_partplate_list();
 | 
			
		||||
    int from_plate = partplate_list.find_instance(curr_obj_id, 0);
 | 
			
		||||
    if (from_plate == -1) {
 | 
			
		||||
        event.Veto();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    auto curr_plate_seq = partplate_list.get_plate(from_plate)->get_print_seq();
 | 
			
		||||
    if (curr_plate_seq == PrintSequence::ByDefault) {
 | 
			
		||||
        auto curr_preset_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
 | 
			
		||||
        if (curr_preset_config.has("print_sequence"))
 | 
			
		||||
            curr_plate_seq = curr_preset_config.option<ConfigOptionEnum<PrintSequence>>("print_sequence")->value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (curr_plate_seq != PrintSequence::ByObject) {
 | 
			
		||||
        //drag forbidden under bylayer mode
 | 
			
		||||
        event.Veto();
 | 
			
		||||
        return;
 | 
			
		||||
| 
						 | 
				
			
			@ -1777,7 +1794,7 @@ void ObjectList::load_subobject(ModelVolumeType type, bool from_galery/* = false
 | 
			
		|||
 | 
			
		||||
    if (type == ModelVolumeType::MODEL_PART)
 | 
			
		||||
        // update printable state on canvas
 | 
			
		||||
        wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object((size_t)obj_idx);
 | 
			
		||||
        wxGetApp().plater()->get_view3D_canvas3D()->update_instance_printable_state_for_object((size_t)obj_idx);
 | 
			
		||||
 | 
			
		||||
    if (items.size() > 1) {
 | 
			
		||||
        m_selection_mode = smVolume;
 | 
			
		||||
| 
						 | 
				
			
			@ -1923,11 +1940,9 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo
 | 
			
		|||
        ModelVolume* new_volume = model_object.add_volume(std::move(mesh), type);
 | 
			
		||||
        new_volume->name = boost::filesystem::path(input_file).filename().string();
 | 
			
		||||
 | 
			
		||||
        // adjust the position according to the bounding box
 | 
			
		||||
        const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box();
 | 
			
		||||
        new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb));
 | 
			
		||||
        auto offset = Vec3d(instance_bb.max.x(), instance_bb.min.y(), instance_bb.min.z()) + 0.5 * mesh_bb.size() - v->get_instance_offset();
 | 
			
		||||
        new_volume->set_offset(v->get_instance_transformation().get_matrix(true).inverse() * offset);
 | 
			
		||||
        // BBS: object_mesh.get_init_shift() keep the relative position
 | 
			
		||||
        TriangleMesh object_mesh = model_object.volumes[0]->mesh();
 | 
			
		||||
        new_volume->set_offset(new_volume->mesh().get_init_shift() - object_mesh.get_init_shift());
 | 
			
		||||
 | 
			
		||||
        // set a default extruder value, since user can't add it manually
 | 
			
		||||
        // BBS
 | 
			
		||||
| 
						 | 
				
			
			@ -2055,7 +2070,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
 | 
			
		|||
    });
 | 
			
		||||
    if (type == ModelVolumeType::MODEL_PART)
 | 
			
		||||
        // update printable state on canvas
 | 
			
		||||
        wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object((size_t)obj_idx);
 | 
			
		||||
        wxGetApp().plater()->get_view3D_canvas3D()->update_instance_printable_state_for_object((size_t)obj_idx);
 | 
			
		||||
 | 
			
		||||
    // apply the instance transform to all volumes and reset instance transform except the offset
 | 
			
		||||
    apply_object_instance_transfrom_to_all_volumes(&model_object);
 | 
			
		||||
| 
						 | 
				
			
			@ -2158,50 +2173,54 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name
 | 
			
		|||
#endif /* _DEBUG */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectList::load_mesh_part(const TriangleMesh& mesh, const wxString& name, bool center)
 | 
			
		||||
int ObjectList::load_mesh_part(const TriangleMesh &mesh, const wxString &name, const TextInfo &text_info, bool is_temp)
 | 
			
		||||
{
 | 
			
		||||
    wxDataViewItem item = GetSelection();
 | 
			
		||||
    // we can add volumes for Object or Instance
 | 
			
		||||
    if (!item || !(m_objects_model->GetItemType(item) & (itObject | itInstance)))
 | 
			
		||||
        return;
 | 
			
		||||
        return -1;
 | 
			
		||||
    const int obj_idx = m_objects_model->GetObjectIdByItem(item);
 | 
			
		||||
 | 
			
		||||
    if (obj_idx < 0) return;
 | 
			
		||||
    if (obj_idx < 0)
 | 
			
		||||
        return -1;
 | 
			
		||||
 | 
			
		||||
    // Get object item, if Instance is selected
 | 
			
		||||
    if (m_objects_model->GetItemType(item) & itInstance)
 | 
			
		||||
        item = m_objects_model->GetItemById(obj_idx);
 | 
			
		||||
 | 
			
		||||
    take_snapshot("Load Mesh Part");
 | 
			
		||||
 | 
			
		||||
    ModelObject* mo = (*m_objects)[obj_idx];
 | 
			
		||||
 | 
			
		||||
    Geometry::Transformation instance_transformation = mo->instances[0]->get_transformation();
 | 
			
		||||
 | 
			
		||||
    // apply the instance transform to all volumes and reset instance transform except the offset
 | 
			
		||||
    apply_object_instance_transfrom_to_all_volumes(mo);
 | 
			
		||||
    apply_object_instance_transfrom_to_all_volumes(mo, !is_temp);
 | 
			
		||||
 | 
			
		||||
    ModelVolume* mv = mo->add_volume(mesh);
 | 
			
		||||
    Vec3d instance_bbox = mo->mesh().bounding_box().size();
 | 
			
		||||
    Vec3d offset = Vec3d(0, 0, instance_bbox[2] / 2);
 | 
			
		||||
    mv->set_offset(offset);
 | 
			
		||||
    ModelVolume *mv     = mo->add_volume(mesh);
 | 
			
		||||
    mv->name = name.ToStdString();
 | 
			
		||||
    if (!text_info.m_text.empty())
 | 
			
		||||
        mv->set_text_info(text_info);
 | 
			
		||||
 | 
			
		||||
    std::vector<ModelVolume*> volumes;
 | 
			
		||||
    volumes.push_back(mv);
 | 
			
		||||
    wxDataViewItemArray items = reorder_volumes_and_get_selection(obj_idx, [volumes](const ModelVolume* volume) {
 | 
			
		||||
        return std::find(volumes.begin(), volumes.end(), volume) != volumes.end(); });
 | 
			
		||||
    if (!is_temp) {
 | 
			
		||||
        std::vector<ModelVolume *> volumes;
 | 
			
		||||
        volumes.push_back(mv);
 | 
			
		||||
        wxDataViewItemArray items = reorder_volumes_and_get_selection(obj_idx, [volumes](const ModelVolume *volume) {
 | 
			
		||||
            return std::find(volumes.begin(), volumes.end(), volume) != volumes.end();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object((size_t)obj_idx);
 | 
			
		||||
        wxGetApp().plater()->get_view3D_canvas3D()->update_instance_printable_state_for_object((size_t) obj_idx);
 | 
			
		||||
 | 
			
		||||
    if (items.size() > 1) {
 | 
			
		||||
        m_selection_mode = smVolume;
 | 
			
		||||
        m_last_selected_item = wxDataViewItem(nullptr);
 | 
			
		||||
        if (items.size() > 1) {
 | 
			
		||||
            m_selection_mode     = smVolume;
 | 
			
		||||
            m_last_selected_item = wxDataViewItem(nullptr);
 | 
			
		||||
        }
 | 
			
		||||
        select_items(items);
 | 
			
		||||
 | 
			
		||||
        selection_changed();
 | 
			
		||||
    }
 | 
			
		||||
    select_items(items);
 | 
			
		||||
 | 
			
		||||
    selection_changed();
 | 
			
		||||
 | 
			
		||||
    //BBS: notify partplate the modify
 | 
			
		||||
    notify_instance_updated(obj_idx);
 | 
			
		||||
    return mo->volumes.size() - 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//BBS
 | 
			
		||||
| 
						 | 
				
			
			@ -4563,7 +4582,7 @@ void ObjectList::instances_to_separated_object(const int obj_idx, const std::set
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    // update printable state for new volumes on canvas3D
 | 
			
		||||
    wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object(new_obj_indx);
 | 
			
		||||
    wxGetApp().plater()->get_view3D_canvas3D()->update_instance_printable_state_for_object(new_obj_indx);
 | 
			
		||||
    update_info_items(new_obj_indx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -4596,7 +4615,7 @@ void ObjectList::instances_to_separated_objects(const int obj_idx)
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    // update printable state for new volumes on canvas3D
 | 
			
		||||
    wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(object_idxs);
 | 
			
		||||
    wxGetApp().plater()->get_view3D_canvas3D()->update_instance_printable_state_for_objects(object_idxs);
 | 
			
		||||
    for (size_t object : object_idxs)
 | 
			
		||||
        update_info_items(object);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4707,10 +4726,13 @@ void ObjectList::fix_through_netfabb()
 | 
			
		|||
        std::string res;
 | 
			
		||||
        if (!fix_model_by_win10_sdk_gui(*(object(obj_idx)), vol_idx, progress_dlg, msg, res))
 | 
			
		||||
            return false;
 | 
			
		||||
        wxGetApp().plater()->changed_mesh(obj_idx);
 | 
			
		||||
 | 
			
		||||
        //wxGetApp().plater()->changed_mesh(obj_idx);
 | 
			
		||||
        object(obj_idx)->ensure_on_bed();
 | 
			
		||||
        plater->changed_mesh(obj_idx);
 | 
			
		||||
 | 
			
		||||
        plater->get_partplate_list().notify_instance_update(obj_idx, 0);
 | 
			
		||||
        plater->sidebar().obj_list()->update_plate_values_for_items();
 | 
			
		||||
 | 
			
		||||
        if (res.empty())
 | 
			
		||||
            succes_models.push_back(model_name);
 | 
			
		||||
        else
 | 
			
		||||
| 
						 | 
				
			
			@ -4719,8 +4741,6 @@ void ObjectList::fix_through_netfabb()
 | 
			
		|||
        update_item_error_icon(obj_idx, vol_idx);
 | 
			
		||||
        update_info_items(obj_idx);
 | 
			
		||||
 | 
			
		||||
        object(obj_idx)->ensure_on_bed();
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -5037,7 +5057,7 @@ void ObjectList::reload_all_plates(bool notify_partplate)
 | 
			
		|||
    m_prevent_canvas_selection_update = false;
 | 
			
		||||
 | 
			
		||||
    // update printable states on canvas
 | 
			
		||||
    wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(obj_idxs);
 | 
			
		||||
    wxGetApp().plater()->get_view3D_canvas3D()->update_instance_printable_state_for_objects(obj_idxs);
 | 
			
		||||
    // update scene
 | 
			
		||||
    wxGetApp().plater()->update();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -5171,7 +5191,7 @@ void ObjectList::toggle_printable_state()
 | 
			
		|||
    obj_idxs.erase(unique(obj_idxs.begin(), obj_idxs.end()), obj_idxs.end());
 | 
			
		||||
 | 
			
		||||
    // update printable state on canvas
 | 
			
		||||
    wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(obj_idxs);
 | 
			
		||||
    wxGetApp().plater()->get_view3D_canvas3D()->update_instance_printable_state_for_objects(obj_idxs);
 | 
			
		||||
 | 
			
		||||
    // update scene
 | 
			
		||||
    wxGetApp().plater()->update();
 | 
			
		||||
| 
						 | 
				
			
			@ -5190,17 +5210,20 @@ bool ObjectList::has_paint_on_segmentation()
 | 
			
		|||
    return m_objects_model->HasInfoItem(InfoItemType::MmuSegmentation);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectList::apply_object_instance_transfrom_to_all_volumes(ModelObject *model_object) {
 | 
			
		||||
void ObjectList::apply_object_instance_transfrom_to_all_volumes(ModelObject *model_object, bool need_update_assemble_matrix)
 | 
			
		||||
{
 | 
			
		||||
    const Geometry::Transformation &instance_transformation  = model_object->instances[0]->get_transformation();
 | 
			
		||||
    Vec3d                           original_instance_center = instance_transformation.get_offset();
 | 
			
		||||
 | 
			
		||||
    // apply the instance_transform(except offset) to assemble_transform
 | 
			
		||||
    Geometry::Transformation instance_transformation_copy = instance_transformation;
 | 
			
		||||
    instance_transformation_copy.set_offset(Vec3d(0, 0, 0)); // remove the effect of offset
 | 
			
		||||
    const Transform3d & instance_inverse_matrix = instance_transformation_copy.get_matrix().inverse();
 | 
			
		||||
    const Transform3d & assemble_matrix = model_object->instances[0]->get_assemble_transformation().get_matrix();
 | 
			
		||||
    Transform3d new_assemble_transform = assemble_matrix * instance_inverse_matrix;
 | 
			
		||||
    model_object->instances[0]->set_assemble_from_transform(new_assemble_transform);
 | 
			
		||||
    if (need_update_assemble_matrix) {
 | 
			
		||||
        // apply the instance_transform(except offset) to assemble_transform
 | 
			
		||||
        Geometry::Transformation instance_transformation_copy = instance_transformation;
 | 
			
		||||
        instance_transformation_copy.set_offset(Vec3d(0, 0, 0)); // remove the effect of offset
 | 
			
		||||
        const Transform3d &instance_inverse_matrix = instance_transformation_copy.get_matrix().inverse();
 | 
			
		||||
        const Transform3d &assemble_matrix         = model_object->instances[0]->get_assemble_transformation().get_matrix();
 | 
			
		||||
        Transform3d        new_assemble_transform  = assemble_matrix * instance_inverse_matrix;
 | 
			
		||||
        model_object->instances[0]->set_assemble_from_transform(new_assemble_transform);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // apply the instance_transform to volumn
 | 
			
		||||
    const Transform3d &transformation_matrix = instance_transformation.get_matrix();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,6 +27,7 @@ class ModelConfig;
 | 
			
		|||
class ModelObject;
 | 
			
		||||
class ModelVolume;
 | 
			
		||||
class TriangleMesh;
 | 
			
		||||
struct TextInfo;
 | 
			
		||||
enum class ModelVolumeType : int;
 | 
			
		||||
 | 
			
		||||
// FIXME: broken build on mac os because of this is missing:
 | 
			
		||||
| 
						 | 
				
			
			@ -283,7 +284,7 @@ public:
 | 
			
		|||
    void                load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center = true);
 | 
			
		||||
    // BBS
 | 
			
		||||
    void                switch_to_object_process();
 | 
			
		||||
    void                load_mesh_part(const TriangleMesh& mesh, const wxString& name, bool center = true);
 | 
			
		||||
    int                 load_mesh_part(const TriangleMesh &mesh, const wxString &name, const TextInfo &text_info, bool is_temp);
 | 
			
		||||
    void                del_object(const int obj_idx, bool refresh_immediately = true);
 | 
			
		||||
    void                del_subobject_item(wxDataViewItem& item);
 | 
			
		||||
    void                del_settings_from_config(const wxDataViewItem& parent_item);
 | 
			
		||||
| 
						 | 
				
			
			@ -454,7 +455,7 @@ private:
 | 
			
		|||
    void OnEditingDone(wxDataViewEvent &event);
 | 
			
		||||
 | 
			
		||||
    // apply the instance transform to all volumes and reset instance transform except the offset
 | 
			
		||||
    void apply_object_instance_transfrom_to_all_volumes(ModelObject *model_object);
 | 
			
		||||
    void apply_object_instance_transfrom_to_all_volumes(ModelObject *model_object, bool need_update_assemble_matrix = true);
 | 
			
		||||
 | 
			
		||||
    std::vector<int> m_columns_width;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -344,7 +344,10 @@ void GridCellFilamentsRenderer::Draw(wxGrid &grid, wxGridCellAttr &attr, wxDC &d
 | 
			
		|||
        dc.SetPen(*wxTRANSPARENT_PEN);
 | 
			
		||||
        dc.SetBrush(wxBrush(attr.GetBackgroundColour()));
 | 
			
		||||
        dc.DrawRectangle(rect);
 | 
			
		||||
        dc.DrawBitmap(*bitmap, wxPoint(rect.x + offset_x, rect.y + offset_y));
 | 
			
		||||
        if ( grid_row->model_volume_type != ModelVolumeType::NEGATIVE_VOLUME) {
 | 
			
		||||
            dc.DrawBitmap(*bitmap, wxPoint(rect.x + offset_x, rect.y + offset_y));
 | 
			
		||||
        }
 | 
			
		||||
       
 | 
			
		||||
        text_rect.x += bitmap_width + grid_cell_border_width * 2;
 | 
			
		||||
        text_rect.width -= (bitmap_width + grid_cell_border_width * 2);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -1968,6 +1971,7 @@ void ObjectGridTable::construct_object_configs(ObjectGrid *object_grid)
 | 
			
		|||
        {
 | 
			
		||||
            ModelVolume* volume = object->volumes[j];
 | 
			
		||||
            ObjectGridRow* volume_grid = new ObjectGridRow(i, j, row_volume);
 | 
			
		||||
            volume_grid->model_volume_type = volume->type();
 | 
			
		||||
            volume_grid->config = &(volume->config);
 | 
			
		||||
            volume_grid->name.value = volume->name;
 | 
			
		||||
            size_t pos = volume_grid->name.value.find_first_not_of(' ');
 | 
			
		||||
| 
						 | 
				
			
			@ -2952,11 +2956,21 @@ void ObjectTablePanel::load_data()
 | 
			
		|||
                        break;
 | 
			
		||||
                    case coEnum:
 | 
			
		||||
                        if (col == ObjectGridTable::col_filaments) {
 | 
			
		||||
                            GridCellFilamentsEditor *filament_editor = new GridCellFilamentsEditor(grid_col->choice_count, grid_col->choices, false, &m_color_bitmaps);
 | 
			
		||||
                            m_object_grid->SetCellEditor(row, col, filament_editor);
 | 
			
		||||
                            m_object_grid->SetCellRenderer(row, col, new GridCellFilamentsRenderer());
 | 
			
		||||
                        } else {
 | 
			
		||||
                            GridCellChoiceEditor *combo_editor = new GridCellChoiceEditor(grid_col->choice_count, grid_col->choices);
 | 
			
		||||
                            if (grid_row->model_volume_type != ModelVolumeType::NEGATIVE_VOLUME) {
 | 
			
		||||
                                GridCellFilamentsEditor* filament_editor = new GridCellFilamentsEditor(grid_col->choice_count, grid_col->choices, false, &m_color_bitmaps);
 | 
			
		||||
                                m_object_grid->SetCellEditor(row, col, filament_editor);
 | 
			
		||||
                                m_object_grid->SetCellRenderer(row, col, new GridCellFilamentsRenderer());
 | 
			
		||||
                            }
 | 
			
		||||
                            else {
 | 
			
		||||
                                m_object_grid->SetCellEditor(row, col, new GridCellTextEditor());
 | 
			
		||||
                                auto gcfil = new GridCellFilamentsRenderer();
 | 
			
		||||
                                m_object_grid->SetCellRenderer(row, col, gcfil);
 | 
			
		||||
                                m_object_grid->SetReadOnly(row, col);
 | 
			
		||||
                                //m_object_grid->SetCellFitMode(row, col,  wxGridFitMode::Ellipsize());
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            GridCellChoiceEditor* combo_editor = new GridCellChoiceEditor(grid_col->choice_count, grid_col->choices);
 | 
			
		||||
                            m_object_grid->SetCellEditor(row, col, combo_editor);
 | 
			
		||||
                            m_object_grid->SetCellRenderer(row, col, new wxGridCellChoiceRenderer());
 | 
			
		||||
                        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -348,6 +348,7 @@ public:
 | 
			
		|||
        ConfigOptionFloat           ori_speed_perimeter;
 | 
			
		||||
 | 
			
		||||
        ModelConfig*                config;
 | 
			
		||||
        ModelVolumeType             model_volume_type;
 | 
			
		||||
 | 
			
		||||
        ObjectGridRow(int obj_id, int vol_id, GridRowType type)
 | 
			
		||||
            : object_id(obj_id), volume_id(vol_id), row_type(type)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -385,9 +385,9 @@ void Preview::sys_color_changed()
 | 
			
		|||
 | 
			
		||||
void Preview::on_tick_changed(Type type)
 | 
			
		||||
{
 | 
			
		||||
    if (type == Type::PausePrint) {
 | 
			
		||||
        m_schedule_background_process();
 | 
			
		||||
    }
 | 
			
		||||
    //if (type == Type::PausePrint) {
 | 
			
		||||
    //    m_schedule_background_process();
 | 
			
		||||
    //}
 | 
			
		||||
    m_keep_current_preview_type = false;
 | 
			
		||||
    reload_print(false);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -485,8 +485,7 @@ void Preview::update_layers_slider_mode()
 | 
			
		|||
        // check if whole model uses just only one extruder
 | 
			
		||||
        if (!plate_extruders.empty()) {
 | 
			
		||||
            //const int extruder = objects[0]->config.has("extruder") ? objects[0]->config.option("extruder")->getInt() : 0;
 | 
			
		||||
            const int extruder = plate_extruders[0];
 | 
			
		||||
            only_extruder = extruder;
 | 
			
		||||
            only_extruder = plate_extruders[0];
 | 
			
		||||
        //    auto is_one_extruder_printed_model = [objects, extruder]() {
 | 
			
		||||
        //        for (ModelObject *object : objects) {
 | 
			
		||||
        //            if (object->config.has("extruder") && object->config.option("extruder")->getInt() != extruder) /*return false*/;
 | 
			
		||||
| 
						 | 
				
			
			@ -552,15 +551,16 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee
 | 
			
		|||
    // Detect and set manipulation mode for double slider
 | 
			
		||||
    update_layers_slider_mode();
 | 
			
		||||
 | 
			
		||||
    Plater *          plater = wxGetApp().plater();
 | 
			
		||||
    CustomGCode::Info ticks_info_from_model;
 | 
			
		||||
    Plater* plater = wxGetApp().plater();
 | 
			
		||||
    //BBS: replace model custom gcode with current plate custom gcode
 | 
			
		||||
    CustomGCode::Info ticks_info_from_curr_plate;
 | 
			
		||||
    if (wxGetApp().is_editor())
 | 
			
		||||
        ticks_info_from_model = plater->model().custom_gcode_per_print_z;
 | 
			
		||||
        ticks_info_from_curr_plate = plater->model().get_curr_plate_custom_gcodes();
 | 
			
		||||
    else {
 | 
			
		||||
        ticks_info_from_model.mode   = CustomGCode::Mode::SingleExtruder;
 | 
			
		||||
        ticks_info_from_model.gcodes = m_canvas->get_custom_gcode_per_print_z();
 | 
			
		||||
        ticks_info_from_curr_plate.mode   = CustomGCode::Mode::SingleExtruder;
 | 
			
		||||
        ticks_info_from_curr_plate.gcodes = m_canvas->get_custom_gcode_per_print_z();
 | 
			
		||||
    }
 | 
			
		||||
    check_layers_slider_values(ticks_info_from_model.gcodes, layers_z);
 | 
			
		||||
    check_layers_slider_values(ticks_info_from_curr_plate.gcodes, layers_z);
 | 
			
		||||
 | 
			
		||||
    // first of all update extruder colors to avoid crash, when we are switching printer preset from MM to SM
 | 
			
		||||
    m_layers_slider->SetExtruderColors(plater->get_extruder_colors_from_plater_config(wxGetApp().is_editor() ? nullptr : m_gcode_result));
 | 
			
		||||
| 
						 | 
				
			
			@ -581,9 +581,11 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
    m_layers_slider->SetSelectionSpan(idx_low, idx_high);
 | 
			
		||||
    m_layers_slider->SetTicksValues(ticks_info_from_model);
 | 
			
		||||
    m_layers_slider->SetTicksValues(ticks_info_from_curr_plate);
 | 
			
		||||
 | 
			
		||||
    bool sequential_print     = (wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_enum<PrintSequence>("print_sequence") == PrintSequence::ByObject);
 | 
			
		||||
    auto curr_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
 | 
			
		||||
    auto curr_print_seq = curr_plate->get_real_print_seq();
 | 
			
		||||
    bool sequential_print = (curr_print_seq == PrintSequence::ByObject);
 | 
			
		||||
    m_layers_slider->SetDrawMode(sequential_print);
 | 
			
		||||
 | 
			
		||||
    auto print_mode_stat = m_gcode_result->print_statistics.modes.front();
 | 
			
		||||
| 
						 | 
				
			
			@ -688,7 +690,8 @@ void Preview::load_print_as_fff(bool keep_z_range, bool only_gcode)
 | 
			
		|||
 | 
			
		||||
        if (!gcode_preview_data_valid) {
 | 
			
		||||
            if (wxGetApp().is_editor())
 | 
			
		||||
                color_print_values = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes;
 | 
			
		||||
                //BBS
 | 
			
		||||
                color_print_values = wxGetApp().plater()->model().get_curr_plate_custom_gcodes().gcodes;
 | 
			
		||||
            else
 | 
			
		||||
                color_print_values = m_canvas->get_custom_gcode_per_print_z();
 | 
			
		||||
            colors.push_back("#808080"); // gray color for pause print or custom G-code
 | 
			
		||||
| 
						 | 
				
			
			@ -703,7 +706,8 @@ void Preview::load_print_as_fff(bool keep_z_range, bool only_gcode)
 | 
			
		|||
 | 
			
		||||
    if (IsShown()) {
 | 
			
		||||
        m_canvas->set_selected_extruder(0);
 | 
			
		||||
        if (gcode_preview_data_valid) {
 | 
			
		||||
        bool is_slice_result_valid = wxGetApp().plater()->get_partplate_list().get_curr_plate()->is_slice_result_valid();
 | 
			
		||||
        if (gcode_preview_data_valid && (is_slice_result_valid || m_only_gcode)) {
 | 
			
		||||
            // Load the real G-code preview.
 | 
			
		||||
            //BBS: add more log
 | 
			
		||||
            BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": will load gcode_preview from result, moves count %1%") % m_gcode_result->moves.size();
 | 
			
		||||
| 
						 | 
				
			
			@ -734,7 +738,8 @@ void Preview::load_print_as_fff(bool keep_z_range, bool only_gcode)
 | 
			
		|||
                (unsigned int)print->extruders().size() :
 | 
			
		||||
                m_canvas->get_gcode_extruders_count();
 | 
			
		||||
            std::vector<Item> gcodes = wxGetApp().is_editor() ?
 | 
			
		||||
                wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes :
 | 
			
		||||
                //BBS
 | 
			
		||||
                wxGetApp().plater()->model().get_curr_plate_custom_gcodes().gcodes :
 | 
			
		||||
                m_canvas->get_custom_gcode_per_print_z();
 | 
			
		||||
            const wxString choice = !gcodes.empty() ?
 | 
			
		||||
                _L("Multicolor Print") :
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -430,7 +430,10 @@ bool load_image(const std::string &filename, wxImage &image)
 | 
			
		|||
        result = image.LoadFile(wxString::FromUTF8(filename.c_str()), wxBITMAP_TYPE_BMP);
 | 
			
		||||
    } else if (boost::algorithm::iends_with(filename, ".jpg")) {
 | 
			
		||||
        result = image.LoadFile(wxString::FromUTF8(filename.c_str()), wxBITMAP_TYPE_JPEG);
 | 
			
		||||
    } else {
 | 
			
		||||
    } else if (boost::algorithm::iends_with(filename, ".jpeg")) {
 | 
			
		||||
        result = image.LoadFile(wxString::FromUTF8(filename.c_str()), wxBITMAP_TYPE_JPEG);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    return result;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -128,6 +128,29 @@ void GLGizmoFdmSupports::render_painter_gizmo() const
 | 
			
		|||
    glsafe(::glDisable(GL_BLEND));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BBS
 | 
			
		||||
bool GLGizmoFdmSupports::on_key_down_select_tool_type(int keyCode) {
 | 
			
		||||
    switch (keyCode)
 | 
			
		||||
    {
 | 
			
		||||
    case 'F':
 | 
			
		||||
        m_current_tool = ImGui::FillButtonIcon;
 | 
			
		||||
        break;
 | 
			
		||||
    case 'S':
 | 
			
		||||
        m_current_tool = ImGui::SphereButtonIcon;
 | 
			
		||||
        break;
 | 
			
		||||
    case 'C':
 | 
			
		||||
        m_current_tool = ImGui::CircleButtonIcon;
 | 
			
		||||
        break;
 | 
			
		||||
    case 'G':
 | 
			
		||||
        m_current_tool = ImGui::GapFillIcon;
 | 
			
		||||
        break;
 | 
			
		||||
    default:
 | 
			
		||||
        return false;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BBS
 | 
			
		||||
void GLGizmoFdmSupports::render_triangles(const Selection& selection) const
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -865,42 +888,22 @@ void GLGizmoFdmSupports::run_thread()
 | 
			
		|||
            goto _finished;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (m_is_tree_support)
 | 
			
		||||
        if (!m_print_instance.print_object->support_layers().size())
 | 
			
		||||
        {
 | 
			
		||||
            if (!m_print_instance.print_object->tree_support_layers().size())
 | 
			
		||||
            {
 | 
			
		||||
                BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ",no tree support layer found, update status to 100%\n";
 | 
			
		||||
                print->set_status(100, L("Support Generated"));
 | 
			
		||||
                goto _finished;
 | 
			
		||||
            }
 | 
			
		||||
            for (const TreeSupportLayer *support_layer : m_print_instance.print_object->tree_support_layers())
 | 
			
		||||
            {
 | 
			
		||||
                for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities)
 | 
			
		||||
                {
 | 
			
		||||
                    _3DScene::extrusionentity_to_verts(extrusion_entity, float(support_layer->print_z), m_print_instance.shift, *m_support_volume);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished extrusionentity_to_verts, update status to 100%";
 | 
			
		||||
            BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ",no support layer found, update status to 100%\n";
 | 
			
		||||
            print->set_status(100, L("Support Generated"));
 | 
			
		||||
            goto _finished;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        for (const SupportLayer *support_layer : m_print_instance.print_object->support_layers())
 | 
			
		||||
        {
 | 
			
		||||
            if (!m_print_instance.print_object->support_layers().size())
 | 
			
		||||
            for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities)
 | 
			
		||||
            {
 | 
			
		||||
                BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ",no support layer found, update status to 100%\n";
 | 
			
		||||
                print->set_status(100, L("Support Generated"));
 | 
			
		||||
                goto _finished;
 | 
			
		||||
                _3DScene::extrusionentity_to_verts(extrusion_entity, float(support_layer->print_z), m_print_instance.shift, *m_support_volume);
 | 
			
		||||
            }
 | 
			
		||||
            for (const SupportLayer *support_layer : m_print_instance.print_object->support_layers())
 | 
			
		||||
            {
 | 
			
		||||
                for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities)
 | 
			
		||||
                {
 | 
			
		||||
                    _3DScene::extrusionentity_to_verts(extrusion_entity, float(support_layer->print_z), m_print_instance.shift, *m_support_volume);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished extrusionentity_to_verts, update status to 100%";
 | 
			
		||||
            print->set_status(100, L("Support Generated"));
 | 
			
		||||
        }
 | 
			
		||||
        BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished extrusionentity_to_verts, update status to 100%";
 | 
			
		||||
        print->set_status(100, L("Support Generated"));
 | 
			
		||||
        
 | 
			
		||||
        record_timestamp();
 | 
			
		||||
    }
 | 
			
		||||
    catch (...) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,6 +24,9 @@ public:
 | 
			
		|||
        state_ready
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    //BBS
 | 
			
		||||
    bool on_key_down_select_tool_type(int keyCode);
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    void on_render_input_window(float x, float y, float bottom_limit) override;
 | 
			
		||||
    std::string on_get_name() const override;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -241,6 +241,34 @@ bool GLGizmoMmuSegmentation::on_number_key_down(int number)
 | 
			
		|||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool GLGizmoMmuSegmentation::on_key_down_select_tool_type(int keyCode) {
 | 
			
		||||
    switch (keyCode)
 | 
			
		||||
    {
 | 
			
		||||
    case 'F':
 | 
			
		||||
        m_current_tool = ImGui::FillButtonIcon;
 | 
			
		||||
        break;
 | 
			
		||||
    case 'T':
 | 
			
		||||
        m_current_tool = ImGui::TriangleButtonIcon;
 | 
			
		||||
        break;
 | 
			
		||||
    case 'S':
 | 
			
		||||
        m_current_tool = ImGui::SphereButtonIcon;
 | 
			
		||||
        break;
 | 
			
		||||
    case 'C':
 | 
			
		||||
        m_current_tool = ImGui::CircleButtonIcon;
 | 
			
		||||
        break;
 | 
			
		||||
    case 'H':
 | 
			
		||||
        m_current_tool = ImGui::HeightRangeIcon;
 | 
			
		||||
        break;
 | 
			
		||||
    case 'G':
 | 
			
		||||
        m_current_tool = ImGui::GapFillIcon;
 | 
			
		||||
        break;
 | 
			
		||||
    default:
 | 
			
		||||
        return false;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void render_extruders_combo(const std::string                       &label,
 | 
			
		||||
                                   const std::vector<std::string>          &extruders,
 | 
			
		||||
                                   const std::vector<std::array<float, 4>> &extruders_colors,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -83,6 +83,7 @@ public:
 | 
			
		|||
 | 
			
		||||
    // BBS
 | 
			
		||||
    bool on_number_key_down(int number);
 | 
			
		||||
    bool on_key_down_select_tool_type(int keyCode);
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    // BBS
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -261,7 +261,7 @@ protected:
 | 
			
		|||
    static constexpr float CursorRadiusMin  = 0.4f; // cannot be zero
 | 
			
		||||
    static constexpr float CursorRadiusMax  = 8.f;
 | 
			
		||||
    static constexpr float CursorRadiusStep = 0.2f;
 | 
			
		||||
    static constexpr float CursorHeightMin = 0.2f; // cannot be zero
 | 
			
		||||
    static constexpr float CursorHeightMin = 0.1f; // cannot be zero
 | 
			
		||||
    static constexpr float CursorHeightMax = 8.f;
 | 
			
		||||
    static constexpr float CursorHeightStep = 0.2f;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -226,10 +226,11 @@ void GLGizmoScale3D::on_render()
 | 
			
		|||
 | 
			
		||||
     //draw connections
 | 
			
		||||
 | 
			
		||||
    if (single_instance || single_volume) {
 | 
			
		||||
    // BBS: when select multiple objects, uniform scale can be deselected, display the connection(4,5)
 | 
			
		||||
    //if (single_instance || single_volume) {
 | 
			
		||||
        glsafe(::glColor4fv(m_grabbers[4].color.data()));
 | 
			
		||||
        render_grabbers_connection(4, 5);
 | 
			
		||||
    }
 | 
			
		||||
    //}
 | 
			
		||||
 | 
			
		||||
    glsafe(::glColor4fv(m_grabbers[2].color.data()));
 | 
			
		||||
    render_grabbers_connection(6, 7);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -78,6 +78,23 @@ void GLGizmoSeam::render_painter_gizmo() const
 | 
			
		|||
    glsafe(::glDisable(GL_BLEND));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BBS
 | 
			
		||||
bool GLGizmoSeam::on_key_down_select_tool_type(int keyCode) {
 | 
			
		||||
    switch (keyCode)
 | 
			
		||||
    {
 | 
			
		||||
    case 'S':
 | 
			
		||||
        m_current_tool = ImGui::SphereButtonIcon;
 | 
			
		||||
        break;
 | 
			
		||||
    case 'C':
 | 
			
		||||
        m_current_tool = ImGui::CircleButtonIcon;
 | 
			
		||||
        break;
 | 
			
		||||
    default:
 | 
			
		||||
        return false;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLGizmoSeam::render_triangles(const Selection& selection) const
 | 
			
		||||
{
 | 
			
		||||
    ClippingPlaneDataWrapper clp_data = this->get_clipping_plane_data();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue