Add lift_min: minimum height to travel between objects

Also change other behaviors to be compliant:
 * At the first move don't split the travel
 * At the first move, if start_gcode_manual and no layer_gcode, don't lift the nozzle so the printer won't "z-dance", whatever where the nozzle is.
Note:it's a hack, please redo it properly when reworking gcode-writer.
 * retract_lift_first_layer is gone back to the old simple behavior (revert b16ecbfc)
 * removed auto extra lift for first layer, now that lift_min exists.
 * with complete_object, don't unlift at object/first layer change, to avoid Z-dance
 * lift_min will be used between brims, skirts, objects but not between object-skirt, object-brim and their object
supermerill/SuperSlicer#1783
supermerill/SuperSlicer#1775
supermerill/SuperSlicer#1575
supermerill/SuperSlicer#599
supermerill/SuperSlicer#429
supermerill/SuperSlicer#395
supermerill/SuperSlicer#241
This commit is contained in:
supermerill 2021-10-31 00:08:11 +02:00
parent b8e173aa30
commit f2a9945435
10 changed files with 188 additions and 111 deletions

View File

@ -5,6 +5,7 @@ group:Size and coordinates
setting:max_print_height
setting:z_offset
setting:z_step
setting:lift_min
group:extruders_count_event:milling_count_event:Capabilities
extruders_count
setting:single_extruder_multi_material

View File

@ -219,16 +219,23 @@ std::string Wipe::wipe(GCode& gcodegen, bool toolchange)
}
//if first layer, ask for a bigger lift for travel to object, to be on the safe side
static inline void set_extra_lift(const Layer& layer, const Print& print, GCodeWriter & writer, int extruder_id) {
static inline void set_extra_lift(const float previous_print_z, const int layer_id, const PrintConfig& print_config, GCodeWriter & writer, int extruder_id) {
//if first layer, ask for a bigger lift for travel to object, to be on the safe side
if (layer.id() == 0 &&
(print.config().retract_lift.get_at(extruder_id) != 0 || print.config().retract_lift_first_layer.get_at(extruder_id))) {
//get biggest first layer height and set extra lift for first travel, to be safe.
double extra_lift_value = 0;
for (const PrintObject* obj : print.objects())
extra_lift_value = std::max(extra_lift_value, print.get_object_first_layer_height(*obj));
writer.set_extra_lift(extra_lift_value * 2);
if (print_config.lift_min.value > 0) {
double retract_lift = 0;
//get the current lift (imo, should be given by the writer... i'm duplicating stuff here)
if(//(previous_print_z == 0 && print_config.retract_lift_above.get_at(writer.tool()->id()) == 0) ||
print_config.retract_lift_above.get_at(writer.tool()->id()) <= previous_print_z + EPSILON
|| (layer_id == 0 && print_config.retract_lift_first_layer.get_at(writer.tool()->id())))
retract_lift = writer.tool()->retract_lift();
// see if it's positive
if (previous_print_z + extra_lift_value + retract_lift < print_config.lift_min.value) {
extra_lift_value = print_config.lift_min.value - previous_print_z - retract_lift;
}
}
if(extra_lift_value > 0)
writer.set_extra_lift(extra_lift_value);
}
static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const Vec2f &wipe_tower_pt)
@ -1398,6 +1405,8 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
// Write the custom start G-code
_writeln(file, start_gcode);
m_last_pos_defined = false;
//flush FanMover buffer to avoid modifying the start gcode if it's manual.
if (this->config().start_gcode_manual && this->m_fan_mover.get() != nullptr) {
std::string to_write = this->m_fan_mover.get()->process_gcode("", true);
@ -1514,8 +1523,10 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
// This happens before Z goes down to layer 0 again, so that no collision happens hopefully.
m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer
m_avoid_crossing_perimeters.use_external_mp_once();
set_extra_lift(m_last_layer_z, 0, print.config(), m_writer, initial_extruder_id);
_write(file, this->retract());
std::string gcode;
//go to origin of the next object (it's 0,0 because we shifted the origin to it)
Polyline polyline = this->travel_to(gcode, Point(0, 0), erNone);
this->write_travel_to(gcode, polyline, "move to origin position for next object");
_write(file, gcode);
@ -1531,6 +1542,8 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
this->_print_first_layer_bed_temperature(file, print, between_objects_gcode, initial_extruder_id, false);
this->_print_first_layer_extruder_temperatures(file, print, between_objects_gcode, initial_extruder_id, false);
_writeln(file, between_objects_gcode);
} else {
set_extra_lift(0, 0, print.config(), m_writer, initial_extruder_id);
}
//reinit the seam placer on the new object
m_seam_placer.init(print);
@ -1555,6 +1568,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
m_second_layer_things_done = false;
prev_object = &object;
}
set_extra_lift(m_last_layer_z, prev_object->layers().back()->id(), print.config(), m_writer, initial_extruder_id /* osef, it's only for the lift_min */);
} else {
// Sort layers by Z.
// All extrusion moves with the same top layer height are extruded uninterrupted.
@ -2263,7 +2277,22 @@ void GCode::process_layer(
print.config().before_layer_gcode.value, m_writer.tool()->id(), &config)
+ "\n";
}
// print z move to next layer UNLESS
// if it's going to the first layer, then we may want to dealy the move in these condition:
// there is no "after layer change gcode" and it's the first move from the unknown
if (print.config().layer_gcode.value.empty() && !m_last_pos_defined && m_config.start_gcode_manual && (
// there is a lift (on the first llyer, so the first move will bring us to the required height
(m_writer.tool()->retract_lift() > 0 && (m_config.retract_lift_above.get_at(m_writer.tool()->id()) == 0 || BOOL_EXTRUDER_CONFIG(retract_lift_first_layer)))
|| // or lift_min is higher than the first layer height.
m_config.lift_min.value > layer.print_z
))
m_delayed_layer_change = this->change_layer(print_z); //HACK for superslicer#1775
else {
//extra lift on layer change if multiple objects
if(single_object_instance_idx == size_t(-1) && (support_layer != nullptr || layers.size() > 1))
set_extra_lift(m_last_layer_z, layer.id(), print.config(), m_writer, first_extruder_id);
gcode += this->change_layer(print_z); // this will increase m_layer_index
}
m_layer = &layer;
if (! print.config().layer_gcode.value.empty()) {
DynamicConfig config;
@ -2478,9 +2507,12 @@ void GCode::process_layer(
m_last_processor_extrusion_role = erWipeTower;
//if first layer, ask for a bigger lift for travel to object, to be on the safe side
set_extra_lift(layer, print, m_writer, extruder_id);
if(single_object_instance_idx == size_t(-1) && m_layer_index == 0)
set_extra_lift(m_last_layer_z, layer.id(), print.config(), m_writer, extruder_id);
if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) {
// before going to and from a global skirt, please ensure you are a a safe height
set_extra_lift(m_last_layer_z, layer.id(), print.config(), m_writer, extruder_id);
const std::pair<size_t, size_t> loops = loops_it->second;
this->set_origin(0., 0.);
m_avoid_crossing_perimeters.use_external_mp();
@ -2504,27 +2536,30 @@ void GCode::process_layer(
// Allow a straight travel move to the first object point if this is the first layer (but don't in next layers).
if (first_layer && loops.first == 0)
m_avoid_crossing_perimeters.disable_once();
// before going to and from a global skirt, please ensure you are a a safe height
set_extra_lift(m_last_layer_z, layer.id(), print.config(), m_writer, extruder_id);
}
// Extrude brim with the extruder of the 1st region.
if (! m_brim_done) {
//if first layer, ask for a bigger lift for travel to object, to be on the safe side
set_extra_lift(layer, print, m_writer, extruder_id);
this->set_origin(0., 0.);
m_avoid_crossing_perimeters.use_external_mp();
gcode += this->extrude_entity(print.brim(), "Brim", m_config.support_material_speed.value);
for (const ExtrusionEntity* brim_entity : print.brim().entities) {
//if first layer, ask for a bigger lift for travel to each brim, to be on the safe side
set_extra_lift(m_last_layer_z, layer.id(), print.config(), m_writer, extruder_id);
gcode += this->extrude_entity(*brim_entity, "Brim", m_config.support_material_speed.value);
}
m_brim_done = true;
m_avoid_crossing_perimeters.use_external_mp(false);
// Allow a straight travel move to the first object point.
m_avoid_crossing_perimeters.disable_once();
//to go to the object-only skirt or brim, or to the object (May be overriden here but I don't care)
set_extra_lift(m_last_layer_z, layer.id(), print.config(), m_writer, extruder_id);
}
//extrude object-only skirt
//TODO: use it also for wiping like the other one (as they are exlusiev)
if (single_object_instance_idx != size_t(-1) && !layers.front().object()->skirt().empty()
&& extruder_id == layer_tools.extruders.front()) {
//if first layer, ask for a bigger lift for travel to object, to be on the safe side
set_extra_lift(layer, print, m_writer, extruder_id);
const PrintObject *print_object = layers.front().object();
this->set_origin(unscale(print_object->instances()[single_object_instance_idx].shift));
@ -2540,8 +2575,6 @@ void GCode::process_layer(
//extrude object-only brim
if (single_object_instance_idx != size_t(-1) && !layers.front().object()->brim().empty()
&& extruder_id == layer_tools.extruders.front()) {
//if first layer, ask for a bigger lift for travel to object, to be on the safe side
set_extra_lift(layer, print, m_writer, extruder_id);
const PrintObject *print_object = layers.front().object();
this->set_origin(unscale(print_object->instances()[single_object_instance_idx].shift));
@ -2566,7 +2599,7 @@ void GCode::process_layer(
for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) {
if (is_anything_overridden && print_wipe_extrusions == 0)
gcode+="; PURGING FINISHED\n";
bool first_object = true;
for (InstanceToPrint &instance_to_print : instances_to_print) {
m_config.apply(instance_to_print.print_object.config(), true);
m_layer = layers[instance_to_print.layer_id].layer();
@ -2592,8 +2625,11 @@ void GCode::process_layer(
m_gcode_label_objects_start += std::string("M486 S") + std::to_string(instance_plater_id) + "\n";
}
}
//if first layer, ask for a bigger lift for travel to object, to be on the safe side
set_extra_lift(layer, print, m_writer, extruder_id);
// ask for a bigger lift for travel to object when moving to another object
if (single_object_instance_idx == size_t(-1) && !first_object)
set_extra_lift(m_last_layer_z, layer.id(), print.config(), m_writer, extruder_id);
else
first_object = false;
// When starting a new object, use the external motion planner for the first travel move.
const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift;
std::pair<const PrintObject*, Point> this_object_copy(&instance_to_print.print_object, offset);
@ -3902,7 +3938,10 @@ std::string GCode::_before_extrude(const ExtrusionPath &path, const std::string
if (m_config.travel_acceleration.value > 0)
travel_acceleration = m_config.travel_acceleration.get_abs_value(acceleration);
}
if (m_writer.get_max_acceleration() > 0) {
acceleration = std::min((double)m_writer.get_max_acceleration(), acceleration);
travel_acceleration = std::min((double)m_writer.get_max_acceleration(), travel_acceleration);
}
if (travel_acceleration == acceleration) {
m_writer.set_acceleration((uint32_t)floor(acceleration + 0.5));
// go to first point of extrusion path (stop at midpoint to let us set the decel speed)
@ -3915,7 +3954,10 @@ std::string GCode::_before_extrude(const ExtrusionPath &path, const std::string
if (!m_last_pos_defined || m_last_pos != path.first_point()) {
Polyline poly_start = this->travel_to(gcode, path.first_point(), path.role());
coordf_t length = poly_start.length();
if (length > SCALED_EPSILON) {
// if length is enough, it's not the hack for first move, and the travel accel is different than the normal accel
// then cut the travel in two to change the accel in-between
// TODO: compute the real point where it should be cut, considering an infinite max speed.
if (length > std::max(scale_d(EPSILON), scale_d(m_config.min_length)) && m_last_pos_defined && floor(travel_acceleration) != floor(acceleration)) {
Polyline poly_end;
coordf_t min_length = scale_d(EXTRUDER_CONFIG_WITH_DEFAULT(nozzle_diameter, 0.5)) * 20;
if (poly_start.size() > 2 && length > min_length * 3) {
@ -3954,7 +3996,18 @@ std::string GCode::_before_extrude(const ExtrusionPath &path, const std::string
_add_object_change_labels(gcode);
// compensate retraction
gcode += this->unretract();
if (m_delayed_layer_change.empty()) {
gcode += m_writer.unlift();//this->unretract();
} else {
//check if an unlift happens
std::string unlift = m_writer.unlift();
if (unlift.empty()) {
unlift = m_delayed_layer_change;
}
m_delayed_layer_change.clear();
gcode += unlift;
}
gcode += m_writer.unretract();
speed = _compute_speed_mm_per_sec(path, speed);
double F = speed * 60; // convert mm/sec to mm/min
@ -4185,7 +4238,9 @@ std::string GCode::retract(bool toolchange)
methods even if we performed wipe, since this will ensure the entire retraction
length is honored in case wipe path was too short. */
gcode += toolchange ? m_writer.retract_for_toolchange() : m_writer.retract();
bool need_lift = !m_writer.tool_is_extruder() || toolchange || (BOOL_EXTRUDER_CONFIG(retract_lift_first_layer) && m_config.print_retract_lift.value != 0 && this->m_layer_index == 0);
bool need_lift = !m_writer.tool_is_extruder() || toolchange
|| (BOOL_EXTRUDER_CONFIG(retract_lift_first_layer) && m_config.print_retract_lift.value != 0 && this->m_layer_index == 0)
|| this->m_writer.get_extra_lift() > 0;
bool last_fill_extusion_role_top_infill = (this->m_last_extrusion_role == ExtrusionRole::erTopSolidInfill || this->m_last_extrusion_role == ExtrusionRole::erIroning);
if(this->m_last_extrusion_role == ExtrusionRole::erGapFill)
last_fill_extusion_role_top_infill = (this->m_last_notgapfill_extrusion_role == ExtrusionRole::erTopSolidInfill || this->m_last_notgapfill_extrusion_role == ExtrusionRole::erIroning);
@ -4198,12 +4253,7 @@ std::string GCode::retract(bool toolchange)
need_lift = true;
}
if (need_lift)
if (m_writer.tool()->retract_length() > 0
|| m_config.use_firmware_retraction
|| (!m_writer.tool_is_extruder() && m_writer.tool()->retract_lift() != 0)
|| (BOOL_EXTRUDER_CONFIG(retract_lift_first_layer) && this->m_layer_index == 0)
)
gcode += m_writer.lift();
gcode += m_writer.lift(this->m_layer_index);
return gcode;
}

View File

@ -349,6 +349,8 @@ private:
// Markers for the Pressure Equalizer to recognize the extrusion type.
// The Pressure Equalizer removes the markers from the final G-code.
bool m_enable_extrusion_role_markers;
// HACK to avoid multiple Z move.
std::string m_delayed_layer_change;
// Keeps track of the last extrusion role passed to the processor
ExtrusionRole m_last_processor_extrusion_role;
// How many times will change_layer() be called?

View File

@ -510,14 +510,14 @@ std::string GCodeWriter::travel_to_z(double z, const std::string &comment)
/* If target Z is lower than current Z but higher than nominal Z
we don't perform the move but we only adjust the nominal Z by
reducing the lift amount that will be used for unlift. */
if (!this->will_move_z(z)) {
// note that if we move but it's lower and we are lifted, we can wait a bit for unlifting, to avoid possible dance on layer change.
if (!this->will_move_z(z) || z < m_pos.z() && m_lifted > EPSILON) {
double nominal_z = m_pos.z() - m_lifted;
m_lifted -= (z - nominal_z);
if (std::abs(m_lifted) < EPSILON)
m_lifted = 0.;
return "";
}
/* In all the other cases, we perform an actual Z move and cancel
the lift. */
m_lifted = 0;
@ -548,7 +548,7 @@ bool GCodeWriter::will_move_z(double z) const
we don't perform an actual Z move. */
if (m_lifted > 0) {
double nominal_z = m_pos.z() - m_lifted;
if (z >= nominal_z && z <= m_pos.z())
if (z >= nominal_z + EPSILON && z <= m_pos.z() - EPSILON)
return false;
}
return true;
@ -698,15 +698,19 @@ std::string GCodeWriter::unretract()
/* If this method is called more than once before calling unlift(),
it will not perform subsequent lifts, even if Z was raised manually
(i.e. with travel_to_z()) and thus _lifted was reduced. */
std::string GCodeWriter::lift()
std::string GCodeWriter::lift(int layer_id)
{
// check whether the above/below conditions are met
double target_lift = 0;
if(this->tool_is_extruder()){
bool can_lift = this->config.retract_lift_first_layer.get_at(m_tool->id()) && layer_id == 0;
if (!can_lift) {
//these two should be in the Tool class methods....
double above = this->config.retract_lift_above.get_at(m_tool->id());
double below = this->config.retract_lift_below.get_at(m_tool->id());
if (m_pos.z() >= above && (below == 0 || m_pos.z() <= below))
can_lift = (m_pos.z() >= above - EPSILON && (below == 0 || m_pos.z() <= below + EPSILON));
}
if(can_lift)
target_lift = m_tool->retract_lift();
} else {
target_lift = m_tool->retract_lift();
@ -717,17 +721,19 @@ std::string GCodeWriter::lift()
target_lift = config_region->print_retract_lift.value;
}
if (this->extra_lift > 0) {
target_lift += this->extra_lift;
this->extra_lift = 0;
// one-time extra lift (often for dangerous travels)
if (this->m_extra_lift > 0) {
target_lift += this->m_extra_lift;
this->m_extra_lift = 0;
}
// compare against epsilon because travel_to_z() does math on it
// and subtracting layer_height from retract_lift might not give
// exactly zero
if (std::abs(m_lifted) < EPSILON && target_lift > 0) {
if (std::abs(m_lifted) < target_lift - EPSILON && target_lift > 0) {
std::string str = this->_travel_to_z(m_pos.z() + target_lift - m_lifted, "lift Z");
m_lifted = target_lift;
return this->_travel_to_z(m_pos.z() + target_lift, "lift Z");
return str;
}
return "";
}

View File

@ -51,6 +51,7 @@ public:
std::string set_fan(uint8_t speed, bool dont_save = false, uint16_t default_tool = 0);
void set_acceleration(uint32_t acceleration);
uint32_t get_acceleration() const;
uint32_t get_max_acceleration() { return this->m_max_acceleration; }
std::string write_acceleration();
std::string reset_e(bool force = false);
std::string update_progress(uint32_t num, uint32_t tot, bool allow_100 = false) const;
@ -73,11 +74,12 @@ public:
std::string retract(bool before_wipe = false);
std::string retract_for_toolchange(bool before_wipe = false);
std::string unretract();
std::string lift();
void set_extra_lift(double extra_zlift) { this->m_extra_lift = extra_zlift; }
double get_extra_lift() { return this->m_extra_lift; }
std::string lift(int layer_id);
std::string unlift();
Vec3d get_position() const { return m_pos; }
void set_extra_lift(double extra_zlift) { this->extra_lift = extra_zlift; }
private:
// Extruders are sorted by their ID, so that binary search is possible.
std::vector<Extruder> m_extruders;
@ -96,14 +98,15 @@ private:
int16_t m_last_temperature_with_offset;
int16_t m_last_bed_temperature;
bool m_last_bed_temperature_reached;
// if positive, it's set, and the next lift wil have this extra lift
double m_extra_lift = 0;
// current lift, to remove from m_pos to have the current height.
double m_lifted;
Vec3d m_pos = Vec3d::Zero();
std::string _travel_to_z(double z, const std::string &comment);
std::string _retract(double length, double restart_extra, double restart_extra_toolchange, const std::string &comment);
// if positive, it's set, and the next lift wil have this extra lift
double extra_lift = 0;
};
} /* namespace Slic3r */

View File

@ -735,6 +735,7 @@ const std::vector<std::string>& Preset::printer_options()
"gcode_precision_e",
"use_relative_e_distances",
"use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
"lift_min",
"min_length",
"max_gcode_per_second",
//FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset.

View File

@ -2590,6 +2590,18 @@ void PrintConfigDef::init_fff_params()
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0));
def = this->add("lift_min", coFloat);
def->label = L("Min height for travel");
def->category = OptionCategory::extruders;
def->tooltip = L("When an extruder travels to an object (from the start position or from an object to another), the nozzle height is guaranteed to be at least at this value."
"\nIt's made to ensure the nozzle won't hit clips or things you have on your bed. But be careful to not put a clip in the 'convex shape' of an object."
"\nSet to 0 to disable.");
def->sidetext = L("mm");
def->min = 0;
def->mode = comExpert;
def->is_vector_extruder = true;
def->set_default_value(new ConfigOptionFloat(0));
def = this->add("machine_limits_usage", coEnum);
def->label = L("How to apply limits");
def->full_label = L("Purpose of Machine Limits");
@ -4036,7 +4048,7 @@ void PrintConfigDef::init_fff_params()
"(1+, 0 to use the current extruder to minimize tool changes).");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInt(1));
def->set_default_value(new ConfigOptionInt(0));
def = this->add("support_material_extrusion_width", coFloatOrPercent);
def->label = L("Support material");
@ -4068,7 +4080,7 @@ void PrintConfigDef::init_fff_params()
"(1+, 0 to use the current extruder to minimize tool changes). This affects raft too.");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInt(1));
def->set_default_value(new ConfigOptionInt(0));
def = this->add("support_material_interface_layers", coInt);
def->label = L("Interface layers");
@ -5915,6 +5927,7 @@ std::unordered_set<std::string> prusa_export_to_remove_keys = {
"infill_dense_algo",
"infill_dense",
"infill_extrusion_spacing",
"lift_min",
"machine_max_acceleration_travel",
"max_speed_reduction",
"milling_after_z",

View File

@ -1338,6 +1338,7 @@ public:
ConfigOptionInts first_layer_temperature;
ConfigOptionInts full_fan_speed_layer;
ConfigOptionFloatOrPercent infill_acceleration;
ConfigOptionFloat lift_min;
ConfigOptionInts max_fan_speed;
ConfigOptionFloatsOrPercents max_layer_height;
ConfigOptionFloat max_print_height;
@ -1442,6 +1443,7 @@ protected:
OPT_PTR(first_layer_temperature);
OPT_PTR(full_fan_speed_layer);
OPT_PTR(infill_acceleration);
OPT_PTR(lift_min);
OPT_PTR(max_fan_speed);
OPT_PTR(max_layer_height);
OPT_PTR(max_print_height);

View File

@ -986,8 +986,7 @@ void Control::draw_ruler(wxDC& dc)
wxCoord pos = get_position_from_value(tick);
draw_ticks_pair(dc, pos, mid, 5);
draw_tick_text(dc, wxPoint(mid, pos), tick);
}
else {
} else {
auto draw_short_ticks = [this, mid](wxDC& dc, double& current_tick, int max_tick) {
while (current_tick < max_tick) {
wxCoord pos = get_position_from_value(lround(current_tick));
@ -1015,7 +1014,7 @@ void Control::draw_ruler(wxDC& dc)
if (m_values[tick] < value)
break;
// short ticks from the last tick to the end of current sequence
assert(! std::isnan(short_tick));
//assert(!std::isnan(short_tick));
draw_short_ticks(dc, short_tick, tick);
sequence++;
}

View File

@ -2965,7 +2965,7 @@ void TabPrinter::toggle_options()
// retract lift above / below only applies if using retract lift
vec.resize(0);
vec = { "retract_lift_above", "retract_lift_below", "retract_lift_top" };
vec = { "retract_lift_above", "retract_lift_below", "retract_lift_top", "retract_lift_first_layer" };
for (auto el : vec) {
field = get_field(el, i);
if (field)