diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index c8998da26f..abf01a11a8 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -90,6 +90,7 @@ const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt"; const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml"; +const std::string WIPE_TOWER_INFORMATION_FILE = "Metadata/Prusa_Slicer_wipe_tower_information.xml"; const std::string CUT_INFORMATION_FILE = "Metadata/Prusa_Slicer_cut_information.xml"; static constexpr const char *RELATIONSHIP_TAG = "Relationship"; @@ -554,6 +555,8 @@ namespace Slic3r { void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_wipe_tower_information_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); + void _extract_wipe_tower_information_from_archive_legacy(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, Model& model); void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& subs_context, const std::string& archive_filename); bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); @@ -766,6 +769,9 @@ namespace Slic3r { } } + // Initialize the wipe tower position (see the end of this function): + model.wipe_tower.position.x() = std::numeric_limits::max(); + // Read root model file if (start_part_stat.m_file_index < num_entries) { try { @@ -822,6 +828,10 @@ namespace Slic3r { // extract slic3r layer config ranges file _extract_custom_gcode_per_print_z_from_archive(archive, stat); } + else if (boost::algorithm::iequals(name, WIPE_TOWER_INFORMATION_FILE)) { + // extract wipe tower information file + _extract_wipe_tower_information_from_archive(archive, stat, model); + } else if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE)) { // extract slic3r model config file if (!_extract_model_config_from_archive(archive, stat, model)) { @@ -836,6 +846,28 @@ namespace Slic3r { } } + + if (model.wipe_tower.position.x() == std::numeric_limits::max()) { + // This is apparently an old project from before PS 2.9.0, which saved wipe tower pos and rotation + // into config, not into Model. Try to load it from the config file. + // First set default in case we do not find it (these were the default values of the config options). + model.wipe_tower.position.x() = 180; + model.wipe_tower.position.y() = 140; + model.wipe_tower.rotation = 0.; + + for (mz_uint i = 0; i < num_entries; ++i) { + if (mz_zip_reader_file_stat(&archive, i, &stat)) { + std::string name(stat.m_filename); + std::replace(name.begin(), name.end(), '\\', '/'); + + if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) { + _extract_wipe_tower_information_from_archive_legacy(archive, stat, model); + break; + } + } + } + } + close_zip_reader(&archive); if (m_version == 0) { @@ -1615,6 +1647,77 @@ namespace Slic3r { } } + void _3MF_Importer::_extract_wipe_tower_information_from_archive(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, Model& model) + { + if (stat.m_uncomp_size > 0) { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading wipe tower information data to buffer"); + return; + } + + std::istringstream iss(buffer); // wrap returned xml to istringstream + pt::ptree main_tree; + pt::read_xml(iss, main_tree); + + try { + auto& node = main_tree.get_child("wipe_tower_information"); + double pos_x = node.get(".position_x"); + double pos_y = node.get(".position_y"); + double rot_deg = node.get(".rotation_deg"); + model.wipe_tower.position = Vec2d(pos_x, pos_y); + model.wipe_tower.rotation = rot_deg; + } catch (const boost::property_tree::ptree_bad_path&) { + // Handles missing node or attribute. + add_error("Error while reading wipe tower information."); + return; + } + + } + } + + void _3MF_Importer::_extract_wipe_tower_information_from_archive_legacy(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, Model& model) + { + if (stat.m_uncomp_size > 0) { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading config data to buffer"); + return; + } + + // Try to find wipe tower data in the config, where pre-2.9.0 slicers saved them. + // Do not load the config as usual, it no longer knows those values. + std::istringstream iss(buffer); + std::string line; + + while (iss) { + std::getline(iss, line); + boost::algorithm::trim_left_if(line, [](char ch) { return std::isspace(ch) || ch == ';'; }); + if (boost::starts_with(line, "wipe_tower_x") || boost::starts_with(line, "wipe_tower_y") || boost::starts_with(line, "wipe_tower_rotation_angle")) { + std::string value_str; + try { + value_str = line.substr(line.find("=") + 1, std::string::npos); + } catch (const std::out_of_range&) { + continue; + } + double val = 0.; + std::istringstream value_ss(value_str); + value_ss >> val; + if (! value_ss.fail()) { + if (boost::starts_with(line, "wipe_tower_x")) + model.wipe_tower.position.x() = val; + else if (boost::starts_with(line, "wipe_tower_y")) + model.wipe_tower.position.y() = val; + else + model.wipe_tower.rotation = val; + } + } + } + } + } + void XMLCALL _3MF_Importer::_handle_start_relationships_element(void *userData, const char *name, const char **attributes) { _3MF_Importer *importer = (_3MF_Importer *) userData; @@ -2645,9 +2748,10 @@ namespace Slic3r { bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model); - bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config); + bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config, const Model& model); bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data); bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config); + bool _add_wipe_tower_information_file_to_archive( mz_zip_archive& archive, Model& model); }; bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64) @@ -2754,10 +2858,19 @@ namespace Slic3r { return false; } + + // Adds wipe tower information ("Metadata/Prusa_Slicer_wipe_tower_information.xml"). + if (!_add_wipe_tower_information_file_to_archive(archive, model)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + // Adds slic3r print config file ("Metadata/Slic3r_PE.config"). // This file contains the content of FullPrintConfing / SLAFullPrintConfig. if (config != nullptr) { - if (!_add_print_config_file_to_archive(archive, *config)) { + if (!_add_print_config_file_to_archive(archive, *config, model)) { close_zip_writer(&archive); boost::filesystem::remove(filename); return false; @@ -3456,16 +3569,39 @@ namespace Slic3r { return true; } - bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config) + bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config, const Model& model) { assert(is_decimal_separator_point()); char buffer[1024]; sprintf(buffer, "; %s\n\n", header_slic3r_generated().c_str()); std::string out = buffer; - for (const std::string &key : config.keys()) - if (key != "compatible_printers") - out += "; " + key + " = " + config.opt_serialize(key) + "\n"; + t_config_option_keys keys = config.keys(); + + // Wipe tower values were historically stored in the config, but they were moved into + // Model in PS 2.9.0. Keep saving the old values to maintain forward compatibility. + for (const std::string s : {"wipe_tower_x", "wipe_tower_y", "wipe_tower_rotation_angle"}) + if (! config.has(s)) + keys.emplace_back(s); + sort_remove_duplicates(keys); + + for (const std::string& key : keys) { + if (key == "compatible_printers") + continue; + + std::string opt_serialized; + + if (key == "wipe_tower_x") + opt_serialized = float_to_string_decimal_point(model.wipe_tower.position.x()); + else if (key == "wipe_tower_y") + opt_serialized = float_to_string_decimal_point(model.wipe_tower.position.y()); + else if (key == "wipe_tower_rotation_angle") + opt_serialized = float_to_string_decimal_point(model.wipe_tower.rotation); + else + opt_serialized = config.opt_serialize(key); + + out += "; " + key + " = " + opt_serialized + "\n"; + } if (!out.empty()) { if (!mz_zip_writer_add_mem(&archive, PRINT_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { @@ -3663,6 +3799,34 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv return true; } +bool _3MF_Exporter::_add_wipe_tower_information_file_to_archive( mz_zip_archive& archive, Model& model) +{ + std::string out = ""; + + pt::ptree tree; + pt::ptree& main_tree = tree.add("wipe_tower_information", ""); + + main_tree.put(".position_x", model.wipe_tower.position.x()); + main_tree.put(".position_y", model.wipe_tower.position.y()); + main_tree.put(".rotation_deg", model.wipe_tower.rotation); + + 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, WIPE_TOWER_INFORMATION_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add wipe tower information file to archive"); + return false; + } + } + + return true; +} + // Perform conversions based on the config values available. static void handle_legacy_project_loaded(unsigned int version_project_file, DynamicPrintConfig& config, const boost::optional& prusaslicer_generator_version) { diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 3df96f2640..e7b35490fe 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -69,6 +69,7 @@ Model& Model::assign_copy(const Model &rhs) // copy custom code per height this->custom_gcode_per_print_z = rhs.custom_gcode_per_print_z; + this->wipe_tower = rhs.wipe_tower; return *this; } @@ -90,6 +91,7 @@ Model& Model::assign_copy(Model &&rhs) // copy custom code per height this->custom_gcode_per_print_z = std::move(rhs.custom_gcode_per_print_z); + this->wipe_tower = rhs.wipe_tower; return *this; } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 29a7e3fcf9..45431e65f3 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -1190,6 +1190,8 @@ private: }; +// Note: The following class does not have to inherit from ObjectID, it is currently +// only used for arrangement. It might be good to refactor this in future. class ModelWipeTower final : public ObjectBase { public: @@ -1199,6 +1201,9 @@ public: bool operator==(const ModelWipeTower& other) const { return position == other.position && rotation == other.rotation; } bool operator!=(const ModelWipeTower& other) const { return !((*this) == other); } + // Assignment operator does not touch the ID! + ModelWipeTower& operator=(const ModelWipeTower& rhs) { position = rhs.position; rotation = rhs.rotation; return *this; } + private: friend class cereal::access; @@ -1214,9 +1219,8 @@ private: // Copy constructor copies the ID. explicit ModelWipeTower(const ModelWipeTower &cfg) = default; - // Disabled methods. - ModelWipeTower(ModelWipeTower &&rhs) = delete; - ModelWipeTower& operator=(const ModelWipeTower &rhs) = delete; + // Disabled methods. + ModelWipeTower(ModelWipeTower &&rhs) = delete; ModelWipeTower& operator=(ModelWipeTower &&rhs) = delete; // For serialization / deserialization of ModelWipeTower composed into another class into the Undo / Redo stack as a separate object. diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 0b54a2d0cd..c2fd948e57 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -485,8 +485,8 @@ static std::vector s_Preset_print_options { "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio", "elefant_foot_compensation", "xy_size_compensation", "resolution", "gcode_resolution", "arc_fitting", - "wipe_tower", "wipe_tower_x", "wipe_tower_y", - "wipe_tower_width", "wipe_tower_cone_angle", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width", + "wipe_tower", + "wipe_tower_width", "wipe_tower_cone_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width", "mmu_segmented_region_interlocking_depth", "wipe_tower_extruder", "wipe_tower_no_sparse_layers", "wipe_tower_extra_flow", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits", "perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", "wall_distribution_count", "min_feature_size", "min_bead_width", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 673cfb6c6f..d9db8d0cb0 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -199,10 +199,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "draft_shield" || opt_key == "skirt_distance" || opt_key == "min_skirt_length" - || opt_key == "ooze_prevention" - || opt_key == "wipe_tower_x" - || opt_key == "wipe_tower_y" - || opt_key == "wipe_tower_rotation_angle") { + || opt_key == "ooze_prevention") { steps.emplace_back(psSkirtBrim); } else if ( opt_key == "first_layer_height" diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 72b721f771..f30d811457 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1070,8 +1070,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // Check the position and rotation of the wipe tower. if (model.wipe_tower != m_model.wipe_tower) update_apply_status(this->invalidate_step(psSkirtBrim)); - m_model.wipe_tower.position = model.wipe_tower.position; - m_model.wipe_tower.rotation = model.wipe_tower.rotation; + m_model.wipe_tower = model.wipe_tower; ModelObjectStatusDB model_object_status_db; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index ccaeb57efd..d5b2acfa14 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3547,20 +3547,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = ""; def->set_default_value(new ConfigOptionBool{ false }); - def = this->add("wipe_tower_x", coFloat); - def->label = L("Position X"); - def->tooltip = L("X coordinate of the left front corner of a wipe tower"); - def->sidetext = L("mm"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(180.)); - - def = this->add("wipe_tower_y", coFloat); - def->label = L("Position Y"); - def->tooltip = L("Y coordinate of the left front corner of a wipe tower"); - def->sidetext = L("mm"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(140.)); - def = this->add("wipe_tower_width", coFloat); def->label = L("Width"); def->tooltip = L("Width of a wipe tower"); @@ -3568,13 +3554,6 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(60.)); - def = this->add("wipe_tower_rotation_angle", coFloat); - def->label = L("Wipe tower rotation angle"); - def->tooltip = L("Wipe tower rotation angle with respect to x-axis."); - def->sidetext = L("°"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.)); - def = this->add("wipe_tower_brim_width", coFloat); def->label = L("Wipe tower brim width"); def->tooltip = L("Wipe tower brim width"); @@ -4821,7 +4800,8 @@ static std::set PrintConfigDef_ignore = { // Disabled in 2.6.0-alpha6, this option is problematic "infill_only_where_needed", "gcode_binary", // Introduced in 2.7.0-alpha1, removed in 2.7.1 (replaced by binary_gcode). - "wiping_volumes_extruders" // Removed in 2.7.3-alpha1. + "wiping_volumes_extruders", // Removed in 2.7.3-alpha1. + "wipe_tower_x", "wipe_tower_y", "wipe_tower_rotation_angle" // Removed in 2.9.0 }; void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 8984f61e1a..1a476f7ff6 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -965,11 +965,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionBools, wipe)) ((ConfigOptionBool, wipe_tower)) ((ConfigOptionFloat, wipe_tower_acceleration)) - ((ConfigOptionFloat, wipe_tower_x)) - ((ConfigOptionFloat, wipe_tower_y)) ((ConfigOptionFloat, wipe_tower_width)) ((ConfigOptionFloat, wipe_tower_per_color_wipe)) - ((ConfigOptionFloat, wipe_tower_rotation_angle)) ((ConfigOptionFloat, wipe_tower_brim_width)) ((ConfigOptionFloat, wipe_tower_cone_angle)) ((ConfigOptionPercent, wipe_tower_extra_spacing)) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 1aec236a4e..ee349cd7fd 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -324,7 +324,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) toggle_field("standby_temperature_delta", have_ooze_prevention); bool have_wipe_tower = config->opt_bool("wipe_tower"); - for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_cone_angle", + for (auto el : { "wipe_tower_width", "wipe_tower_brim_width", "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_extra_flow", "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" }) toggle_field(el, have_wipe_tower); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 55dfe8df89..681d12ca83 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -614,7 +614,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) , config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({ "bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance", "brim_width", "brim_separation", "brim_type", "variable_layer_height", "nozzle_diameter", "single_extruder_multi_material", - "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_extra_flow", "wipe_tower_extruder", + "wipe_tower", "wipe_tower_width", "wipe_tower_brim_width", "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_extra_flow", "wipe_tower_extruder", "extruder_colour", "filament_colour", "material_colour", "max_print_height", "printer_model", "printer_notes", "printer_technology", // These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor. "layer_height", "first_layer_height", "min_layer_height", "max_layer_height", @@ -1300,8 +1300,10 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (! config_substitutions.empty()) show_substitutions_info(config_substitutions.substitutions, filename.string()); - if (load_config) + if (load_config) { this->model.custom_gcode_per_print_z = model.custom_gcode_per_print_z; + this->model.wipe_tower = model.wipe_tower; + } } if (load_config) { diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index b566381e23..88ceaa54be 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1635,10 +1635,7 @@ void TabPrint::build() optgroup = page->new_optgroup(L("Wipe tower")); optgroup->append_single_option_line("wipe_tower"); - optgroup->append_single_option_line("wipe_tower_x"); - optgroup->append_single_option_line("wipe_tower_y"); - optgroup->append_single_option_line("wipe_tower_width"); - optgroup->append_single_option_line("wipe_tower_rotation_angle"); + optgroup->append_single_option_line("wipe_tower_width"); optgroup->append_single_option_line("wipe_tower_brim_width"); optgroup->append_single_option_line("wipe_tower_bridging"); optgroup->append_single_option_line("wipe_tower_cone_angle");