2017-11-09 10:48:06 +01:00
#include <cassert>
2018-11-26 14:41:58 +01:00
#include "libslic3r/Flow.hpp"
2020-02-05 16:53:26 +01:00
#include "libslic3r/Slicing.hpp"
2018-11-26 14:41:58 +01:00
#include "libslic3r/libslic3r.h"
2017-11-09 10:48:06 +01:00
#include "PresetHints.hpp"
2018-02-22 11:34:41 +01:00
#include <wx/intl.h>
2017-11-09 10:48:06 +01:00
2018-02-22 11:34:41 +01:00
#include "GUI.hpp"
2020-12-04 13:17:18 +01:00
#include "format.hpp"
2018-11-26 14:41:58 +01:00
#include "I18N.hpp"
2017-11-09 10:48:06 +01:00
namespace Slic3r {
2018-03-06 09:44:53 +01:00
#define MIN_BUF_LENGTH 4096
2017-11-09 10:48:06 +01:00
std :: string PresetHints :: cooling_description ( const Preset & preset )
{
std :: string out ;
2019-05-11 23:29:25 +02:00
2020-12-04 11:53:02 +01:00
bool cooling = preset . config . opt_bool ( "cooling" , 0 );
int fan_below_layer_time = preset . config . opt_int ( "fan_below_layer_time" , 0 );
int full_fan_speed_layer = preset . config . opt_int ( "full_fan_speed_layer" , 0 );
if ( cooling ) {
2017-11-09 10:48:06 +01:00
int slowdown_below_layer_time = preset . config . opt_int ( "slowdown_below_layer_time" , 0 );
int min_fan_speed = preset . config . opt_int ( "min_fan_speed" , 0 );
int max_fan_speed = preset . config . opt_int ( "max_fan_speed" , 0 );
int min_print_speed = int ( preset . config . opt_float ( "min_print_speed" , 0 ) + 0.5 );
2019-05-11 23:29:25 +02:00
2020-12-04 11:53:02 +01:00
out += GUI :: format ( _L ( "If estimated layer time is below ~%1%s, "
"fan will run at %2%%% and print speed will be reduced "
"so that no less than %3%s are spent on that layer "
"(however, speed will never be reduced below %4%mm/s)." ),
slowdown_below_layer_time , max_fan_speed , slowdown_below_layer_time , min_print_speed );
if ( fan_below_layer_time > slowdown_below_layer_time )
out += " \n " +
GUI :: format ( _L ( "If estimated layer time is greater, but still below ~%1%s, "
"fan will run at a proportionally decreasing speed between %2%%% and %3%%%." ),
fan_below_layer_time , max_fan_speed , min_fan_speed );
out += " \n " ;
2017-11-09 10:48:06 +01:00
}
if ( preset . config . opt_bool ( "fan_always_on" , 0 )) {
int disable_fan_first_layers = preset . config . opt_int ( "disable_fan_first_layers" , 0 );
int min_fan_speed = preset . config . opt_int ( "min_fan_speed" , 0 );
2019-05-11 23:29:25 +02:00
2020-12-04 13:05:25 +01:00
if ( full_fan_speed_layer > disable_fan_first_layers + 1 )
2020-12-04 11:53:02 +01:00
out += GUI :: format ( _L ( "Fan speed will be ramped from zero at layer %1% to %2%%% at layer %3%." ), disable_fan_first_layers , min_fan_speed , full_fan_speed_layer );
else {
out += GUI :: format ( cooling ? _L ( "During the other layers, fan will always run at %1%%%" ) : _L ( "Fan will always run at %1%%%" ), min_fan_speed ) + " " ;
if ( disable_fan_first_layers > 1 )
out += GUI :: format ( _L ( "except for the first %1% layers." ), disable_fan_first_layers );
else if ( disable_fan_first_layers == 1 )
out += GUI :: format ( _L ( "except for the first layer." ));
}
2017-11-09 10:48:06 +01:00
} else
2020-12-04 11:53:02 +01:00
out += cooling ? _u8L ( "During the other layers, fan will be turned off." ) : _u8L ( "Fan will be turned off." );
2017-11-09 10:48:06 +01:00
return out ;
}
2017-11-09 15:10:20 +01:00
static const ConfigOptionFloatOrPercent & first_positive ( const ConfigOptionFloatOrPercent * v1 , const ConfigOptionFloatOrPercent & v2 , const ConfigOptionFloatOrPercent & v3 )
{
return ( v1 != nullptr && v1 -> value > 0 ) ? * v1 : (( v2 . value > 0 ) ? v2 : v3 );
}
2017-11-09 10:48:06 +01:00
std :: string PresetHints :: maximum_volumetric_flow_description ( const PresetBundle & preset_bundle )
{
// Find out, to which nozzle index is the current filament profile assigned.
2017-11-09 15:10:20 +01:00
int idx_extruder = 0 ;
int num_extruders = ( int ) preset_bundle . filament_presets . size ();
for (; idx_extruder < num_extruders ; ++ idx_extruder )
2019-05-22 11:31:36 +02:00
if ( preset_bundle . filament_presets [ idx_extruder ] == preset_bundle . filaments . get_selected_preset_name ())
2017-11-09 10:48:06 +01:00
break ;
2017-11-09 15:10:20 +01:00
if ( idx_extruder == num_extruders )
2017-11-09 10:48:06 +01:00
// The current filament preset is not active for any extruder.
2017-11-09 15:10:20 +01:00
idx_extruder = - 1 ;
2017-11-09 10:48:06 +01:00
const DynamicPrintConfig & print_config = preset_bundle . prints . get_edited_preset (). config ;
const DynamicPrintConfig & filament_config = preset_bundle . filaments . get_edited_preset (). config ;
const DynamicPrintConfig & printer_config = preset_bundle . printers . get_edited_preset (). config ;
// Current printer values.
2017-11-09 15:10:20 +01:00
float nozzle_diameter = ( float ) printer_config . opt_float ( "nozzle_diameter" , idx_extruder );
2017-11-09 10:48:06 +01:00
// Print config values
2017-11-09 15:10:20 +01:00
double layer_height = print_config . opt_float ( "layer_height" );
2017-11-09 10:48:06 +01:00
double first_layer_height = print_config . get_abs_value ( "first_layer_height" , layer_height );
double support_material_speed = print_config . opt_float ( "support_material_speed" );
double support_material_interface_speed = print_config . get_abs_value ( "support_material_interface_speed" , support_material_speed );
double bridge_speed = print_config . opt_float ( "bridge_speed" );
double bridge_flow_ratio = print_config . opt_float ( "bridge_flow_ratio" );
double perimeter_speed = print_config . opt_float ( "perimeter_speed" );
double external_perimeter_speed = print_config . get_abs_value ( "external_perimeter_speed" , perimeter_speed );
2019-09-03 10:27:16 +02:00
// double gap_fill_speed = print_config.opt_float("gap_fill_speed");
2017-11-09 10:48:06 +01:00
double infill_speed = print_config . opt_float ( "infill_speed" );
double small_perimeter_speed = print_config . get_abs_value ( "small_perimeter_speed" , perimeter_speed );
double solid_infill_speed = print_config . get_abs_value ( "solid_infill_speed" , infill_speed );
double top_solid_infill_speed = print_config . get_abs_value ( "top_solid_infill_speed" , solid_infill_speed );
// Maximum print speed when auto-speed is enabled by setting any of the above speed values to zero.
double max_print_speed = print_config . opt_float ( "max_print_speed" );
// Maximum volumetric speed allowed for the print profile.
double max_volumetric_speed = print_config . opt_float ( "max_volumetric_speed" );
2017-11-09 15:10:20 +01:00
const auto & extrusion_width = * print_config . option < ConfigOptionFloatOrPercent > ( "extrusion_width" );
2017-11-09 10:48:06 +01:00
const auto & external_perimeter_extrusion_width = * print_config . option < ConfigOptionFloatOrPercent > ( "external_perimeter_extrusion_width" );
const auto & first_layer_extrusion_width = * print_config . option < ConfigOptionFloatOrPercent > ( "first_layer_extrusion_width" );
2017-11-09 15:10:20 +01:00
const auto & infill_extrusion_width = * print_config . option < ConfigOptionFloatOrPercent > ( "infill_extrusion_width" );
const auto & perimeter_extrusion_width = * print_config . option < ConfigOptionFloatOrPercent > ( "perimeter_extrusion_width" );
const auto & solid_infill_extrusion_width = * print_config . option < ConfigOptionFloatOrPercent > ( "solid_infill_extrusion_width" );
const auto & support_material_extrusion_width = * print_config . option < ConfigOptionFloatOrPercent > ( "support_material_extrusion_width" );
const auto & top_infill_extrusion_width = * print_config . option < ConfigOptionFloatOrPercent > ( "top_infill_extrusion_width" );
2017-12-20 14:51:18 +01:00
const auto & first_layer_speed = * print_config . option < ConfigOptionFloatOrPercent > ( "first_layer_speed" );
2017-11-09 10:48:06 +01:00
2017-11-09 15:10:20 +01:00
// Index of an extruder assigned to a feature. If set to 0, an active extruder will be used for a multi-material print.
// If different from idx_extruder, it will not be taken into account for this hint.
auto feature_extruder_active = [ idx_extruder , num_extruders ]( int i ) {
return i <= 0 || i > num_extruders || idx_extruder == - 1 || idx_extruder == i - 1 ;
};
bool perimeter_extruder_active = feature_extruder_active ( print_config . opt_int ( "perimeter_extruder" ));
bool infill_extruder_active = feature_extruder_active ( print_config . opt_int ( "infill_extruder" ));
bool solid_infill_extruder_active = feature_extruder_active ( print_config . opt_int ( "solid_infill_extruder" ));
bool support_material_extruder_active = feature_extruder_active ( print_config . opt_int ( "support_material_extruder" ));
bool support_material_interface_extruder_active = feature_extruder_active ( print_config . opt_int ( "support_material_interface_extruder" ));
2017-11-09 10:48:06 +01:00
// Current filament values
double filament_diameter = filament_config . opt_float ( "filament_diameter" , 0 );
2017-11-09 15:10:20 +01:00
double filament_crossection = M_PI * 0.25 * filament_diameter * filament_diameter ;
2019-09-03 10:27:16 +02:00
// double extrusion_multiplier = filament_config.opt_float("extrusion_multiplier", 0);
2017-11-09 15:10:20 +01:00
// The following value will be annotated by this hint, so it does not take part in the calculation.
// double filament_max_volumetric_speed = filament_config.opt_float("filament_max_volumetric_speed", 0);
2017-11-09 10:48:06 +01:00
std :: string out ;
2017-11-09 15:10:20 +01:00
for ( size_t idx_type = ( first_layer_extrusion_width . value == 0 ) ? 1 : 0 ; idx_type < 3 ; ++ idx_type ) {
// First test the maximum volumetric extrusion speed for non-bridging extrusions.
bool first_layer = idx_type == 0 ;
bool bridging = idx_type == 2 ;
const ConfigOptionFloatOrPercent * first_layer_extrusion_width_ptr = ( first_layer && first_layer_extrusion_width . value > 0 ) ?
& first_layer_extrusion_width : nullptr ;
const float lh = float ( first_layer ? first_layer_height : layer_height );
const float bfr = bridging ? bridge_flow_ratio : 0.f ;
double max_flow = 0. ;
std :: string max_flow_extrusion_type ;
2017-12-20 14:51:18 +01:00
auto limit_by_first_layer_speed = [ & first_layer_speed , first_layer ]( double speed_normal , double speed_max ) {
if ( first_layer && first_layer_speed . value > 0 )
// Apply the first layer limit.
speed_normal = first_layer_speed . get_abs_value ( speed_normal );
return ( speed_normal > 0. ) ? speed_normal : speed_max ;
};
2017-11-09 15:10:20 +01:00
if ( perimeter_extruder_active ) {
double external_perimeter_rate = Flow :: new_from_config_width ( frExternalPerimeter ,
first_positive ( first_layer_extrusion_width_ptr , external_perimeter_extrusion_width , extrusion_width ),
nozzle_diameter , lh , bfr ). mm3_per_mm () *
( bridging ? bridge_speed :
2017-12-20 14:51:18 +01:00
limit_by_first_layer_speed ( std :: max ( external_perimeter_speed , small_perimeter_speed ), max_print_speed ));
2017-11-09 15:10:20 +01:00
if ( max_flow < external_perimeter_rate ) {
max_flow = external_perimeter_rate ;
2019-05-11 23:29:25 +02:00
max_flow_extrusion_type = _utf8 ( L ( "external perimeters" ));
2017-11-09 15:10:20 +01:00
}
double perimeter_rate = Flow :: new_from_config_width ( frPerimeter ,
first_positive ( first_layer_extrusion_width_ptr , perimeter_extrusion_width , extrusion_width ),
nozzle_diameter , lh , bfr ). mm3_per_mm () *
( bridging ? bridge_speed :
2017-12-20 14:51:18 +01:00
limit_by_first_layer_speed ( std :: max ( perimeter_speed , small_perimeter_speed ), max_print_speed ));
2017-11-09 15:10:20 +01:00
if ( max_flow < perimeter_rate ) {
max_flow = perimeter_rate ;
2019-05-11 23:29:25 +02:00
max_flow_extrusion_type = _utf8 ( L ( "perimeters" ));
2017-11-09 15:10:20 +01:00
}
2017-11-09 10:48:06 +01:00
}
2017-11-09 15:10:20 +01:00
if ( ! bridging && infill_extruder_active ) {
double infill_rate = Flow :: new_from_config_width ( frInfill ,
first_positive ( first_layer_extrusion_width_ptr , infill_extrusion_width , extrusion_width ),
2017-12-20 14:51:18 +01:00
nozzle_diameter , lh , bfr ). mm3_per_mm () * limit_by_first_layer_speed ( infill_speed , max_print_speed );
2017-11-09 15:10:20 +01:00
if ( max_flow < infill_rate ) {
max_flow = infill_rate ;
2019-05-11 23:29:25 +02:00
max_flow_extrusion_type = _utf8 ( L ( "infill" ));
2017-11-09 15:10:20 +01:00
}
}
if ( solid_infill_extruder_active ) {
double solid_infill_rate = Flow :: new_from_config_width ( frInfill ,
first_positive ( first_layer_extrusion_width_ptr , solid_infill_extrusion_width , extrusion_width ),
nozzle_diameter , lh , 0 ). mm3_per_mm () *
2017-12-20 14:51:18 +01:00
( bridging ? bridge_speed : limit_by_first_layer_speed ( solid_infill_speed , max_print_speed ));
2017-11-09 15:10:20 +01:00
if ( max_flow < solid_infill_rate ) {
max_flow = solid_infill_rate ;
2019-05-11 23:29:25 +02:00
max_flow_extrusion_type = _utf8 ( L ( "solid infill" ));
2017-11-09 15:10:20 +01:00
}
if ( ! bridging ) {
double top_solid_infill_rate = Flow :: new_from_config_width ( frInfill ,
first_positive ( first_layer_extrusion_width_ptr , top_infill_extrusion_width , extrusion_width ),
2017-12-20 14:51:18 +01:00
nozzle_diameter , lh , bfr ). mm3_per_mm () * limit_by_first_layer_speed ( top_solid_infill_speed , max_print_speed );
2017-11-09 15:10:20 +01:00
if ( max_flow < top_solid_infill_rate ) {
max_flow = top_solid_infill_rate ;
2019-05-11 23:29:25 +02:00
max_flow_extrusion_type = _utf8 ( L ( "top solid infill" ));
2017-11-09 15:10:20 +01:00
}
}
}
if ( support_material_extruder_active ) {
double support_material_rate = Flow :: new_from_config_width ( frSupportMaterial ,
first_positive ( first_layer_extrusion_width_ptr , support_material_extrusion_width , extrusion_width ),
nozzle_diameter , lh , bfr ). mm3_per_mm () *
2017-12-20 14:51:18 +01:00
( bridging ? bridge_speed : limit_by_first_layer_speed ( support_material_speed , max_print_speed ));
2017-11-09 15:10:20 +01:00
if ( max_flow < support_material_rate ) {
max_flow = support_material_rate ;
2019-05-11 23:29:25 +02:00
max_flow_extrusion_type = _utf8 ( L ( "support" ));
2017-11-09 15:10:20 +01:00
}
}
if ( support_material_interface_extruder_active ) {
double support_material_interface_rate = Flow :: new_from_config_width ( frSupportMaterialInterface ,
first_positive ( first_layer_extrusion_width_ptr , support_material_extrusion_width , extrusion_width ),
nozzle_diameter , lh , bfr ). mm3_per_mm () *
2017-12-20 14:51:18 +01:00
( bridging ? bridge_speed : limit_by_first_layer_speed ( support_material_interface_speed , max_print_speed ));
2017-11-09 15:10:20 +01:00
if ( max_flow < support_material_interface_rate ) {
max_flow = support_material_interface_rate ;
2019-05-11 23:29:25 +02:00
max_flow_extrusion_type = _utf8 ( L ( "support interface" ));
2017-11-09 15:10:20 +01:00
}
}
//FIXME handle gap_fill_speed
if ( ! out . empty ())
out += " \n " ;
2019-05-11 23:29:25 +02:00
out += ( first_layer ? _utf8 ( L ( "First layer volumetric" )) : ( bridging ? _utf8 ( L ( "Bridging volumetric" )) : _utf8 ( L ( "Volumetric" ))));
out += " " + _utf8 ( L ( "flow rate is maximized" )) + " " ;
2017-12-20 14:51:18 +01:00
bool limited_by_max_volumetric_speed = max_volumetric_speed > 0 && max_volumetric_speed < max_flow ;
out += ( limited_by_max_volumetric_speed ?
2019-05-11 23:29:25 +02:00
_utf8 ( L ( "by the print profile maximum" )) :
( _utf8 ( L ( "when printing" )) + " " + max_flow_extrusion_type ))
+ " " + _utf8 ( L ( "with a volumetric rate" )) + " " ;
2017-12-20 14:51:18 +01:00
if ( limited_by_max_volumetric_speed )
2017-11-09 15:10:20 +01:00
max_flow = max_volumetric_speed ;
2019-05-11 23:29:25 +02:00
out += ( boost :: format ( _utf8 ( L ( "%3.2f mm³/s at filament speed %3.2f mm/s." ))) % max_flow % ( max_flow / filament_crossection )). str ();
2017-11-09 10:48:06 +01:00
}
2017-11-09 15:10:20 +01:00
return out ;
2017-11-09 10:48:06 +01:00
}
2018-01-04 15:38:06 +01:00
std :: string PresetHints :: recommended_thin_wall_thickness ( const PresetBundle & preset_bundle )
{
const DynamicPrintConfig & print_config = preset_bundle . prints . get_edited_preset (). config ;
const DynamicPrintConfig & printer_config = preset_bundle . printers . get_edited_preset (). config ;
float layer_height = float ( print_config . opt_float ( "layer_height" ));
int num_perimeters = print_config . opt_int ( "perimeters" );
bool thin_walls = print_config . opt_bool ( "thin_walls" );
float nozzle_diameter = float ( printer_config . opt_float ( "nozzle_diameter" , 0 ));
2018-02-22 11:34:41 +01:00
std :: string out ;
2020-02-05 16:53:26 +01:00
if ( layer_height <= 0.f ) {
2019-05-11 23:29:25 +02:00
out += _utf8 ( L ( "Recommended object thin wall thickness: Not available due to invalid layer height." ));
2018-02-22 11:34:41 +01:00
return out ;
}
2018-02-12 15:37:42 +01:00
2018-01-04 15:38:06 +01:00
Flow external_perimeter_flow = Flow :: new_from_config_width (
frExternalPerimeter ,
* print_config . opt < ConfigOptionFloatOrPercent > ( "external_perimeter_extrusion_width" ),
nozzle_diameter , layer_height , false );
Flow perimeter_flow = Flow :: new_from_config_width (
frPerimeter ,
* print_config . opt < ConfigOptionFloatOrPercent > ( "perimeter_extrusion_width" ),
nozzle_diameter , layer_height , false );
2018-02-22 11:34:41 +01:00
2018-01-04 15:38:06 +01:00
if ( num_perimeters > 0 ) {
int num_lines = std :: min ( num_perimeters * 2 , 10 );
2019-05-11 23:29:25 +02:00
out += ( boost :: format ( _utf8 ( L ( "Recommended object thin wall thickness for layer height %.2f and" ))) % layer_height ). str () + " " ;
2018-01-04 15:38:06 +01:00
// Start with the width of two closely spaced
2020-02-18 12:26:48 +01:00
try {
double width = external_perimeter_flow . width + external_perimeter_flow . spacing ();
for ( int i = 2 ; i <= num_lines ; thin_walls ? ++ i : i += 2 ) {
if ( i > 2 )
out += ", " ;
out += ( boost :: format ( _utf8 ( L ( "%d lines: %.2f mm" ))) % i % width ). str () + " " ;
width += perimeter_flow . spacing () * ( thin_walls ? 1.f : 2.f );
}
} catch ( const FlowErrorNegativeSpacing & ) {
out = _utf8 ( L ( "Recommended object thin wall thickness: Not available due to excessively small extrusion width." ));
2018-01-04 15:38:06 +01:00
}
}
return out ;
}
2020-02-05 16:53:26 +01:00
// Produce a textual explanation of the combined effects of the top/bottom_solid_layers
// versus top/bottom_min_shell_thickness. Which of the two values wins depends
// on the active layer height.
std :: string PresetHints :: top_bottom_shell_thickness_explanation ( const PresetBundle & preset_bundle )
{
const DynamicPrintConfig & print_config = preset_bundle . prints . get_edited_preset (). config ;
const DynamicPrintConfig & printer_config = preset_bundle . printers . get_edited_preset (). config ;
std :: string out ;
int top_solid_layers = print_config . opt_int ( "top_solid_layers" );
int bottom_solid_layers = print_config . opt_int ( "bottom_solid_layers" );
bool has_top_layers = top_solid_layers > 0 ;
bool has_bottom_layers = bottom_solid_layers > 0 ;
double top_solid_min_thickness = print_config . opt_float ( "top_solid_min_thickness" );
double bottom_solid_min_thickness = print_config . opt_float ( "bottom_solid_min_thickness" );
double layer_height = print_config . opt_float ( "layer_height" );
bool variable_layer_height = printer_config . opt_bool ( "variable_layer_height" );
2020-04-22 12:49:52 +02:00
//FIXME the following line takes into account the 1st extruder only.
2020-02-06 11:00:27 +01:00
double min_layer_height = variable_layer_height ? Slicing :: min_layer_height_from_nozzle ( printer_config , 1 ) : layer_height ;
2020-02-05 16:53:26 +01:00
if ( layer_height <= 0.f ) {
out += _utf8 ( L ( "Top / bottom shell thickness hint: Not available due to invalid layer height." ));
return out ;
}
if ( has_top_layers ) {
double top_shell_thickness = top_solid_layers * layer_height ;
if ( top_shell_thickness < top_solid_min_thickness ) {
// top_solid_min_shell_thickness triggers even in case of normal layer height. Round the top_shell_thickness up
// to an integer multiply of layer_height.
double n = ceil ( top_solid_min_thickness / layer_height );
top_shell_thickness = n * layer_height ;
}
double top_shell_thickness_minimum = std :: max ( top_solid_min_thickness , top_solid_layers * min_layer_height );
out += ( boost :: format ( _utf8 ( L ( "Top shell is %1% mm thick for layer height %2% mm." ))) % top_shell_thickness % layer_height ). str ();
if ( variable_layer_height && top_shell_thickness_minimum < top_shell_thickness ) {
out += " " ;
out += ( boost :: format ( _utf8 ( L ( "Minimum top shell thickness is %1% mm." ))) % top_shell_thickness_minimum ). str ();
}
2020-02-06 11:00:27 +01:00
} else
out += _utf8 ( L ( "Top is open." ));
out += " \n " ;
2020-02-05 16:53:26 +01:00
if ( has_bottom_layers ) {
double bottom_shell_thickness = bottom_solid_layers * layer_height ;
if ( bottom_shell_thickness < bottom_solid_min_thickness ) {
// bottom_solid_min_shell_thickness triggers even in case of normal layer height. Round the bottom_shell_thickness up
// to an integer multiply of layer_height.
double n = ceil ( bottom_solid_min_thickness / layer_height );
bottom_shell_thickness = n * layer_height ;
}
double bottom_shell_thickness_minimum = std :: max ( bottom_solid_min_thickness , bottom_solid_layers * min_layer_height );
out += ( boost :: format ( _utf8 ( L ( "Bottom shell is %1% mm thick for layer height %2% mm." ))) % bottom_shell_thickness % layer_height ). str ();
if ( variable_layer_height && bottom_shell_thickness_minimum < bottom_shell_thickness ) {
out += " " ;
out += ( boost :: format ( _utf8 ( L ( "Minimum bottom shell thickness is %1% mm." ))) % bottom_shell_thickness_minimum ). str ();
}
2020-02-06 11:00:27 +01:00
} else
out += _utf8 ( L ( "Bottom is open." ));
2020-02-05 16:53:26 +01:00
return out ;
}
2017-11-09 10:48:06 +01:00
}; // namespace Slic3r