From 2ebd14667e43dc745556f5e7bcbb7c2ccad4a007 Mon Sep 17 00:00:00 2001 From: "xun.zhang" Date: Fri, 23 Aug 2024 19:47:00 +0800 Subject: [PATCH] ENH: add some params for multi extruder 1. Nozzle Volume and Nozzle Type support multi extruder now jira:NONE Signed-off-by: xun.zhang Change-Id: Ie171b5105bd3830db3a992cadd365b785008c47a --- src/libslic3r/GCode/GCodeProcessor.cpp | 116 +++++++++++++++---------- src/libslic3r/GCode/GCodeProcessor.hpp | 4 +- src/libslic3r/PrintConfig.cpp | 21 +++-- src/libslic3r/PrintConfig.hpp | 4 +- src/slic3r/GUI/PartPlate.hpp | 2 + src/slic3r/GUI/Plater.cpp | 4 +- src/slic3r/GUI/SelectMachine.cpp | 31 ++++--- src/slic3r/Utils/CalibUtils.cpp | 40 ++++++--- 8 files changed, 140 insertions(+), 82 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 2efcff53a..89558d881 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1077,19 +1077,27 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_flavor = config.gcode_flavor; // BBS - size_t extruders_count = config.filament_diameter.values.size(); - m_result.filaments_count = extruders_count; + size_t filament_count = config.filament_diameter.values.size(); + m_result.filaments_count = filament_count; - m_extruder_offsets.resize(extruders_count); - m_extruder_colors.resize(extruders_count); - m_result.filament_diameters.resize(extruders_count); - m_result.required_nozzle_HRC.resize(extruders_count); - m_result.filament_densities.resize(extruders_count); - m_result.filament_vitrification_temperature.resize(extruders_count); - m_result.filament_costs.resize(extruders_count); - m_extruder_temps.resize(extruders_count); - m_result.nozzle_type = config.nozzle_type; - for (size_t i = 0; i < extruders_count; ++ i) { + assert(config.nozzle_volume.size() == config.nozzle_diameter.size()); + m_nozzle_volume.resize(config.nozzle_volume.size()); + for (size_t idx = 0; idx < config.nozzle_volume.size(); ++idx) + m_nozzle_volume[idx] = config.nozzle_volume.values[idx]; + + m_extruder_offsets.resize(filament_count); + m_extruder_colors.resize(filament_count); + m_result.filament_diameters.resize(filament_count); + m_result.required_nozzle_HRC.resize(filament_count); + m_result.filament_densities.resize(filament_count); + m_result.filament_vitrification_temperature.resize(filament_count); + m_result.filament_costs.resize(filament_count); + m_extruder_temps.resize(filament_count); + std::vector(config.nozzle_type.size()).swap(m_result.nozzle_type); + for (size_t idx = 0; idx < m_result.nozzle_type.size(); ++idx) { + m_result.nozzle_type[idx] = NozzleType(config.nozzle_type.values[idx]); + } + for (size_t i = 0; i < filament_count; ++ i) { m_extruder_offsets[i] = to_3d(config.extruder_offset.get_at(i).cast().eval(), 0.f); m_extruder_colors[i] = static_cast(i); m_result.filament_diameters[i] = static_cast(config.filament_diameter.get_at(i)); @@ -1153,13 +1161,20 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) m_parser.apply_config(config); //BBS - const ConfigOptionFloat* nozzle_volume = config.option("nozzle_volume"); - if (nozzle_volume != nullptr) - m_nozzle_volume = nozzle_volume->value; + const ConfigOptionFloatsNullable* nozzle_volume = config.option("nozzle_volume"); + if (nozzle_volume != nullptr) { + m_nozzle_volume.resize(nozzle_volume->size(), 0); + for (size_t idx = 0; idx < nozzle_volume->size(); ++idx) + m_nozzle_volume[idx] = nozzle_volume->values[idx]; + } - const ConfigOptionEnum* nozzle_type = config.option>("nozzle_type"); - if (nozzle_type != nullptr) - m_result.nozzle_type=nozzle_type->value; + const ConfigOptionEnumsGenericNullable* nozzle_type = config.option("nozzle_type"); + if (nozzle_type != nullptr) { + m_result.nozzle_type.resize(nozzle_type->size()); + for (size_t idx = 0; idx < nozzle_type->values.size(); ++idx) { + m_result.nozzle_type[idx] = NozzleType(nozzle_type->values[idx]); + } + } const ConfigOptionEnum* gcode_flavor = config.option>("gcode_flavor"); if (gcode_flavor != nullptr) @@ -1459,7 +1474,7 @@ void GCodeProcessor::reset() m_e_local_positioning_type = EPositioningType::Absolute; m_extruder_offsets = std::vector(MIN_EXTRUDERS_COUNT, Vec3f::Zero()); m_flavor = gcfRepRapSprinter; - m_nozzle_volume = 0.f; + m_nozzle_volume = {0.f,0.f}; m_start_position = { 0.0f, 0.0f, 0.0f, 0.0f }; m_end_position = { 0.0f, 0.0f, 0.0f, 0.0f }; @@ -1468,7 +1483,7 @@ void GCodeProcessor::reset() m_wiping = false; m_flushing = false; m_wipe_tower = false; - m_remaining_volume = 0.f; + m_remaining_volume = { 0.f,0.f }; // BBS: arc move related data m_move_path_type = EMovePathType::Noop_move; m_arc_center = Vec3f::Zero(); @@ -3034,16 +3049,17 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } else if (type == EMoveType::Unretract && m_flushing) { + int extruder_id = get_extruder_id(); float volume_flushed_filament = area_filament_cross_section * delta_pos[E]; - if (m_remaining_volume > volume_flushed_filament) + if (m_remaining_volume[extruder_id] > volume_flushed_filament) { m_used_filaments.update_flush_per_filament(last_filament_id, volume_flushed_filament); - m_remaining_volume -= volume_flushed_filament; + m_remaining_volume[extruder_id] -= volume_flushed_filament; } else { - m_used_filaments.update_flush_per_filament(last_filament_id, m_remaining_volume); - m_used_filaments.update_flush_per_filament(filament_id, volume_flushed_filament - m_remaining_volume); - m_remaining_volume = 0.f; + m_used_filaments.update_flush_per_filament(last_filament_id, m_remaining_volume[extruder_id]); + m_used_filaments.update_flush_per_filament(filament_id, volume_flushed_filament - m_remaining_volume[extruder_id]); + m_remaining_volume[extruder_id] = 0.f; } } @@ -4623,7 +4639,8 @@ void GCodeProcessor::process_filaments(CustomGCode::Type code) m_used_filaments.process_support_cache(this); m_used_filaments.process_total_volume_cache(this); //BBS: reset remaining filament - m_remaining_volume = m_nozzle_volume; + size_t last_extruder_id = get_extruder_id(); + m_remaining_volume[last_extruder_id] = m_nozzle_volume[last_extruder_id]; } } @@ -4670,24 +4687,24 @@ void GCodeProcessor::update_slice_warnings() { m_result.warnings.clear(); - auto get_used_extruders = [this]() { - std::vector used_extruders; - used_extruders.reserve(m_used_filaments.total_volumes_per_filament.size()); + auto get_used_filaments = [this]() { + std::vector used_filaments; + used_filaments.reserve(m_used_filaments.total_volumes_per_filament.size()); for (auto item : m_used_filaments.total_volumes_per_filament) { - used_extruders.push_back(item.first); + used_filaments.push_back(item.first); } - return used_extruders; + return used_filaments; }; - auto used_extruders = get_used_extruders(); - assert(!used_extruders.empty()); + auto used_filaments = get_used_filaments(); + assert(!used_filaments.empty()); GCodeProcessorResult::SliceWarning warning; warning.level = 1; if (m_highest_bed_temp != 0) { - for (size_t i = 0; i < used_extruders.size(); i++) { - int temperature = get_filament_vitrification_temperature(used_extruders[i]); + for (size_t i = 0; i < used_filaments.size(); i++) { + int temperature = get_filament_vitrification_temperature(used_filaments[i]); if (temperature != 0 && m_highest_bed_temp >= temperature) - warning.params.push_back(std::to_string(used_extruders[i])); + warning.params.push_back(std::to_string(used_filaments[i])); } } @@ -4701,15 +4718,24 @@ void GCodeProcessor::update_slice_warnings() warning.params.clear(); warning.level=1; - int nozzle_hrc = Print::get_hrc_by_nozzle_type(m_result.nozzle_type); - if (nozzle_hrc!=0) { - for (size_t i = 0; i < used_extruders.size(); i++) { - int HRC=0; - if (used_extruders[i] < m_result.required_nozzle_HRC.size()) - HRC = m_result.required_nozzle_HRC[used_extruders[i]]; - if (HRC != 0 && (nozzle_hrcnozzle_hrc_lists(m_result.nozzle_type.size(), 0); + // store the nozzle hrc of each extruder + for (size_t idx = 0; idx < m_result.nozzle_type.size(); ++idx) + nozzle_hrc_lists[idx] = Print::get_hrc_by_nozzle_type(m_result.nozzle_type[idx]); + + for (size_t idx = 0; idx < used_filaments.size(); ++idx) { + int filament_hrc = 0; + + if (used_filaments[idx] < m_result.required_nozzle_HRC.size()) + filament_hrc = m_result.required_nozzle_HRC[used_filaments[idx]]; + + int filament_extruder_id = m_filament_maps[used_filaments[idx]]; + int extruder_hrc = nozzle_hrc_lists[filament_extruder_id]; + + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": Check HRC: filament:%1%, hrc=%2%, extruder:%3%, hrc:%4%") % used_filaments[idx] % filament_hrc % filament_extruder_id % extruder_hrc; + + if (extruder_hrc!=0 && extruder_hrc < filament_hrc) + warning.params.push_back(std::to_string(used_filaments[idx])); } if (!warning.params.empty()) { diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 1313c8995..af8b06c63 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -222,7 +222,7 @@ namespace Slic3r { std::vector>> spiral_vase_layers; //BBS std::vector warnings; - NozzleType nozzle_type; + std::vector nozzle_type; BedType bed_type = BedType::btCount; #if ENABLE_GCODE_VIEWER_STATISTICS int64_t time{ 0 }; @@ -735,7 +735,7 @@ namespace Slic3r { EPositioningType m_e_local_positioning_type; std::vector m_extruder_offsets; GCodeFlavor m_flavor; - float m_nozzle_volume; + std::vector m_nozzle_volume; AxisCoords m_start_position; // mm AxisCoords m_end_position; // mm AxisCoords m_origin; // mm diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 01093d469..e13f16dc2 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2161,7 +2161,7 @@ void PrintConfigDef::init_fff_params() // def->mode = comSimple; // def->set_default_value(new ConfigOptionBool(false)); - def = this->add("nozzle_type", coEnum); + def = this->add("nozzle_type", coEnums); def->label = L("Nozzle type"); def->tooltip = L("The metallic material of nozzle. This determines the abrasive resistance of nozzle, and " "what kind of filament can be printed"); @@ -2175,7 +2175,8 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Stainless steel")); def->enum_labels.push_back(L("Brass")); def->mode = comDevelop; - def->set_default_value(new ConfigOptionEnum(ntUndefine)); + def->nullable = true; + def->set_default_value(new ConfigOptionEnumsGenericNullable({ ntUndefine })); def = this->add("printer_structure", coEnum); def->label = L("Printer structure"); @@ -2780,13 +2781,14 @@ void PrintConfigDef::init_fff_params() def->cli = ConfigOptionDef::nocli; def->set_default_value(new ConfigOptionEnum(htOctoPrint)); - def = this->add("nozzle_volume", coFloat); + def = this->add("nozzle_volume", coFloats); def->label = L("Nozzle volume"); def->tooltip = L("Volume of nozzle between the cutter and the end of nozzle"); def->sidetext = L("mm³"); def->mode = comAdvanced; def->readonly = true; - def->set_default_value(new ConfigOptionFloat { 0.0 }); + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable { {0.0} }); def = this->add("start_end_points", coPoints); def->label = L("Start end points"); @@ -5136,6 +5138,8 @@ const PrintConfigDef print_config_def; //todo std::set print_options_with_variant = { + "initial_layer_speed", + "initial_layer_infill_speed", "outer_wall_speed", "inner_wall_speed", "small_perimeter_speed", @@ -5150,8 +5154,8 @@ std::set print_options_with_variant = { "overhang_4_4_speed", "bridge_speed", "gap_infill_speed", - "initial_layer_speed", - "initial_layer_infill_speed", + "support_speed", + "support_interface_speed", "travel_speed", "travel_speed_z", "default_acceleration", @@ -5160,8 +5164,6 @@ std::set print_options_with_variant = { "inner_wall_acceleration", "sparse_infill_acceleration", "top_surface_acceleration", - "support_interface_speed", - "support_speed", "print_extruder_id", "print_extruder_variant" }; @@ -5215,6 +5217,8 @@ std::set printer_options_with_variant_1 = { "retract_restart_extra_toolchange", "long_retractions_when_cut", "retraction_distances_when_cut", + "nozzle_volume", + "nozzle_type", "printer_extruder_id", "printer_extruder_variant" }; @@ -5482,6 +5486,7 @@ size_t DynamicPrintConfig::get_parameter_size(const std::string& param_name, siz if (nozzle_volume_type_opt) { volume_type_size = nozzle_volume_type_opt->values.size(); } + bool flag = (param_name == "nozzle_volume"); if (printer_options_with_variant_1.count(param_name) > 0) { return extruder_nums * volume_type_size; } diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 637347e24..d589856e7 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1028,7 +1028,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionString, machine_pause_gcode)) ((ConfigOptionString, template_custom_gcode)) //BBS - ((ConfigOptionEnum, nozzle_type)) + ((ConfigOptionEnumsGenericNullable,nozzle_type)) ((ConfigOptionEnum,printer_structure)) ((ConfigOptionBool, auxiliary_fan)) ((ConfigOptionBool, support_chamber_temp_control)) @@ -1162,7 +1162,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloats, filament_colour_new)) // BBS: not in any preset, calculated before slicing ((ConfigOptionBool, has_prime_tower)) - ((ConfigOptionFloat, nozzle_volume)) + ((ConfigOptionFloatsNullable, nozzle_volume)) ((ConfigOptionPoints, start_end_points)) ((ConfigOptionEnum, timelapse_type)) ((ConfigOptionFloat, default_jerk)) diff --git a/src/slic3r/GUI/PartPlate.hpp b/src/slic3r/GUI/PartPlate.hpp index ebdf61b81..a2bd369a4 100644 --- a/src/slic3r/GUI/PartPlate.hpp +++ b/src/slic3r/GUI/PartPlate.hpp @@ -293,6 +293,7 @@ public: std::vector get_extruders(bool conside_custom_gcode = false) const; std::vector get_extruders_under_cli(bool conside_custom_gcode, DynamicPrintConfig& full_config) const; std::vector get_extruders_without_support(bool conside_custom_gcode = false) const; + // get used filaments, 1 based idx std::vector get_used_extruders(); /* instance related operations*/ @@ -483,6 +484,7 @@ public: bool has_auto_filament_map_reslut(); void set_auto_filament_map_result(bool has_result); + // get filament map, 0 based filament ids, 1 based extruder ids std::vector get_filament_maps(); void set_filament_maps(const std::vector& f_maps); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e3f23d7f3..03f95062c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -496,8 +496,8 @@ std::vector get_min_flush_volumes(const DynamicPrintConfig &full_config, si //const auto& full_config = wxGetApp().preset_bundle->full_config(); //auto& printer_config = wxGetApp().preset_bundle->printers.get_edited_preset().config; - const ConfigOption* nozzle_volume_opt = full_config.option("nozzle_volume"); - int nozzle_volume_val = nozzle_volume_opt ? (int)nozzle_volume_opt->getFloat() : 0; + const ConfigOptionFloatsNullable* nozzle_volume_opt = full_config.option("nozzle_volume"); + int nozzle_volume_val = nozzle_volume_opt ? (int)nozzle_volume_opt->get_at(nozzle_id) : 0; const ConfigOptionInt* enable_long_retraction_when_cut_opt = full_config.option("enable_long_retraction_when_cut"); int machine_enabled_level = 0; diff --git a/src/slic3r/GUI/SelectMachine.cpp b/src/slic3r/GUI/SelectMachine.cpp index e3cf5fb71..731105ce9 100644 --- a/src/slic3r/GUI/SelectMachine.cpp +++ b/src/slic3r/GUI/SelectMachine.cpp @@ -2611,21 +2611,32 @@ bool SelectMachineDialog::is_same_nozzle_diameters(std::string& tag_nozzle_type, PresetBundle* preset_bundle = wxGetApp().preset_bundle; auto opt_nozzle_diameters = preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter"); - const ConfigOptionEnum* nozzle_type = preset_bundle->printers.get_edited_preset().config.option>("nozzle_type"); + const ConfigOptionEnumsGenericNullable* nozzle_type = preset_bundle->printers.get_edited_preset().config.option("nozzle_type"); + std::vector preset_nozzle_types(nozzle_type->size()); + for (size_t idx = 0; idx < nozzle_type->size(); ++idx) + preset_nozzle_types[idx] = NozzleTypeEumnToStr[NozzleType(nozzle_type->values[idx])]; - if (nozzle_type->value == NozzleType::ntHardenedSteel) { - preset_nozzle_type = "hardened_steel"; - } - else if (nozzle_type->value == NozzleType::ntStainlessSteel) { - preset_nozzle_type = "stainless_steel"; - } + std::vector machine_nozzle_types(obj_->m_nozzle_data.nozzles.size()); + for (size_t idx = 0; idx < obj_->m_nozzle_data.nozzles.size(); ++idx) + machine_nozzle_types[idx] = obj_->m_nozzle_data.nozzles[idx].type; + auto used_filaments = wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_used_extruders(); // 1 based + auto filament_maps=wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_filament_maps(); // 1 based + + std::vectorused_extruders; // 0 based + for (auto f : used_filaments) { + int filament_extruder = filament_maps[f - 1] - 1; + if (std::find(used_extruders.begin(), used_extruders.end(), filament_extruder) == used_extruders.end()) + used_extruders.emplace_back(filament_extruder); + } + std::sort(used_extruders.begin(), used_extruders.end()); + + // TODO [tao wang] : add idx mapping tag_nozzle_type = obj_->m_nozzle_data.nozzles[0].type; - auto extruders = wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_used_extruders(); if (opt_nozzle_diameters != nullptr) { - for (auto i = 0; i < extruders.size(); i++) { - auto extruder = extruders[i] - 1; + for (auto i = 0; i < used_extruders.size(); i++) { + auto extruder = used_extruders[i]; preset_nozzle_diameters = float(opt_nozzle_diameters->get_at(extruder)); if (preset_nozzle_diameters != obj_->m_nozzle_data.nozzles[0].diameter) { is_same_nozzle_diameters = false; diff --git a/src/slic3r/Utils/CalibUtils.cpp b/src/slic3r/Utils/CalibUtils.cpp index a604a083b..62e157323 100644 --- a/src/slic3r/Utils/CalibUtils.cpp +++ b/src/slic3r/Utils/CalibUtils.cpp @@ -101,22 +101,36 @@ static bool is_same_nozzle_diameters(const DynamicPrintConfig &full_config, cons try { std::string nozzle_type; - const ConfigOptionEnum * config_nozzle_type = full_config.option>("nozzle_type"); - if (config_nozzle_type->value == NozzleType::ntHardenedSteel) { - nozzle_type = "hardened_steel"; - } else if (config_nozzle_type->value == NozzleType::ntStainlessSteel) { - nozzle_type = "stainless_steel"; - } + + const ConfigOptionEnumsGenericNullable * config_nozzle_type = full_config.option("nozzle_type"); + std::vector config_nozzle_types_str(config_nozzle_type->size()); + for (size_t idx = 0; idx < config_nozzle_type->size(); ++idx) + config_nozzle_types_str[idx] = NozzleTypeEumnToStr[NozzleType(config_nozzle_type->values[idx])]; auto opt_nozzle_diameters = full_config.option("nozzle_diameter"); - if (opt_nozzle_diameters != nullptr) { - float preset_nozzle_diameter = opt_nozzle_diameters->get_at(0); - if (preset_nozzle_diameter != obj->m_nozzle_data.nozzles[0].diameter) { - wxString nozzle_in_preset = wxString::Format(_L("nozzle in preset: %s %s"), wxString::Format("%.1f", preset_nozzle_diameter).ToStdString(), to_wstring_name(nozzle_type)); - wxString nozzle_in_printer = wxString::Format(_L("nozzle memorized: %.1f %s"), obj->m_nozzle_data.nozzles[0].diameter, DeviceManager::nozzle_type_conver(obj->m_nozzle_data.nozzles[0].diameter)); - error_msg = _L("Your nozzle diameter in preset is not consistent with memorized nozzle diameter. Did you change your nozzle lately?") + "\n " + nozzle_in_preset + - "\n " + nozzle_in_printer + "\n"; + std::vector config_nozzle_diameters(opt_nozzle_diameters->size()); + for (size_t idx = 0; idx < opt_nozzle_diameters->size(); ++idx) + config_nozzle_diameters[idx] = opt_nozzle_diameters->values[idx]; + + std::vector machine_nozzle_diameters(obj->m_nozzle_data.nozzles.size()); + for (size_t idx = 0; idx < obj->m_nozzle_data.nozzles.size(); ++idx) + machine_nozzle_diameters[idx] = obj->m_nozzle_data.nozzles[idx].diameter; + + if (config_nozzle_diameters.size() != machine_nozzle_diameters.size()) { + wxString nozzle_in_preset = wxString::Format(_L("nozzle size in preset: %d"), config_nozzle_diameters.size()); + wxString nozzle_in_printer = wxString::Format(_L("nozzle size memorized: %d"), machine_nozzle_diameters.size()); + error_msg = _L("The size of nozzle type in preset is not consistent with memorized nozzle.Did you change your nozzle lately ? ") + "\n " + nozzle_in_preset + + "\n " + nozzle_in_printer + "\n"; + return false; + } + + for (size_t idx = 0; idx < config_nozzle_diameters.size(); ++idx) { + if (config_nozzle_diameters[idx] != machine_nozzle_diameters[idx]) { + wxString nozzle_in_preset = wxString::Format(_L("nozzle[%d] in preset: %.1f"), idx, config_nozzle_diameters[idx]); + wxString nozzle_in_printer = wxString::Format(_L("nozzle[%d] memorized: %.1f"), idx, machine_nozzle_diameters[idx]); + error_msg = _L("Your nozzle type in preset is not consistent with memorized nozzle.Did you change your nozzle lately ? ") + "\n " + nozzle_in_preset + + "\n " + nozzle_in_printer + "\n"; return false; } }