2014-01-05 12:16:13 +00:00
# include "Flow.hpp"
2020-02-04 13:43:58 +00:00
# include "I18N.hpp"
2017-05-03 16:28:22 +00:00
# include "Print.hpp"
2014-01-05 12:16:13 +00:00
# include <cmath>
2015-12-11 20:36:51 +00:00
# include <assert.h>
2014-01-05 12:16:13 +00:00
2020-02-05 08:57:22 +00:00
# include <boost/algorithm/string/predicate.hpp>
2020-02-04 13:43:58 +00:00
// Mark string for localization and translate.
# define L(s) Slic3r::I18N::translate(s)
2014-01-05 12:16:13 +00:00
namespace Slic3r {
2020-02-18 11:26:48 +00:00
FlowErrorNegativeSpacing : : FlowErrorNegativeSpacing ( ) :
FlowError ( " Flow::spacing() produced negative spacing. Did you set some extrusion width too small? " ) { }
FlowErrorNegativeFlow : : FlowErrorNegativeFlow ( ) :
FlowError ( " Flow::mm3_per_mm() produced negative flow. Did you set some extrusion width too small? " ) { }
2017-07-19 13:53:43 +00:00
// This static method returns a sane extrusion width default.
2020-02-04 13:43:58 +00:00
float Flow : : auto_extrusion_width ( FlowRole role , float nozzle_diameter )
2017-07-19 13:53:43 +00:00
{
switch ( role ) {
case frSupportMaterial :
case frSupportMaterialInterface :
case frTopSolidInfill :
return nozzle_diameter ;
default :
case frExternalPerimeter :
case frPerimeter :
case frSolidInfill :
case frInfill :
2017-07-20 09:03:54 +00:00
return 1.125f * nozzle_diameter ;
2017-07-19 13:53:43 +00:00
}
}
2020-02-04 13:43:58 +00:00
// Used by the Flow::extrusion_width() funtion to provide hints to the user on default extrusion width values,
// and to provide reasonable values to the PlaceholderParser.
static inline FlowRole opt_key_to_flow_role ( const std : : string & opt_key )
{
if ( opt_key = = " perimeter_extrusion_width " | |
// or all the defaults:
opt_key = = " extrusion_width " | | opt_key = = " first_layer_extrusion_width " )
return frPerimeter ;
else if ( opt_key = = " external_perimeter_extrusion_width " )
return frExternalPerimeter ;
else if ( opt_key = = " infill_extrusion_width " )
return frInfill ;
else if ( opt_key = = " solid_infill_extrusion_width " )
return frSolidInfill ;
else if ( opt_key = = " top_infill_extrusion_width " )
return frTopSolidInfill ;
else if ( opt_key = = " support_material_extrusion_width " )
return frSupportMaterial ;
else
2020-09-14 14:27:55 +00:00
throw Slic3r : : RuntimeError ( " opt_key_to_flow_role: invalid argument " ) ;
2020-02-04 13:43:58 +00:00
} ;
static inline void throw_on_missing_variable ( const std : : string & opt_key , const char * dependent_opt_key )
{
2020-02-18 11:26:48 +00:00
throw FlowErrorMissingVariable ( ( boost : : format ( L ( " Cannot calculate extrusion width for %1%: Variable \" %2% \" not accessible. " ) ) % opt_key % dependent_opt_key ) . str ( ) ) ;
2020-02-04 13:43:58 +00:00
}
// Used to provide hints to the user on default extrusion width values, and to provide reasonable values to the PlaceholderParser.
double Flow : : extrusion_width ( const std : : string & opt_key , const ConfigOptionFloatOrPercent * opt , const ConfigOptionResolver & config , const unsigned int first_printing_extruder )
{
assert ( opt ! = nullptr ) ;
bool first_layer = boost : : starts_with ( opt_key , " first_layer_ " ) ;
#if 0
// This is the logic used for skit / brim, but not for the rest of the 1st layer.
if ( opt - > value = = 0. & & first_layer ) {
// The "first_layer_extrusion_width" was set to zero, try a substitute.
opt = config . option < ConfigOptionFloatOrPercent > ( " perimeter_extrusion_width " ) ;
if ( opt = = nullptr )
throw_on_missing_variable ( opt_key , " perimeter_extrusion_width " ) ;
}
# endif
if ( opt - > value = = 0. ) {
// The role specific extrusion width value was set to zero, try the role non-specific extrusion width.
opt = config . option < ConfigOptionFloatOrPercent > ( " extrusion_width " ) ;
if ( opt = = nullptr )
throw_on_missing_variable ( opt_key , " extrusion_width " ) ;
// Use the "layer_height" instead of "first_layer_height".
first_layer = false ;
}
if ( opt - > percent ) {
auto opt_key_layer_height = first_layer ? " first_layer_height " : " layer_height " ;
auto opt_layer_height = config . option ( opt_key_layer_height ) ;
if ( opt_layer_height = = nullptr )
throw_on_missing_variable ( opt_key , opt_key_layer_height ) ;
double layer_height = opt_layer_height - > getFloat ( ) ;
if ( first_layer & & static_cast < const ConfigOptionFloatOrPercent * > ( opt_layer_height ) - > percent ) {
// first_layer_height depends on layer_height.
opt_layer_height = config . option ( " layer_height " ) ;
if ( opt_layer_height = = nullptr )
throw_on_missing_variable ( opt_key , " layer_height " ) ;
layer_height * = 0.01 * opt_layer_height - > getFloat ( ) ;
}
return opt - > get_abs_value ( layer_height ) ;
}
if ( opt - > value = = 0. ) {
// If user left option to 0, calculate a sane default width.
auto opt_nozzle_diameters = config . option < ConfigOptionFloats > ( " nozzle_diameter " ) ;
if ( opt_nozzle_diameters = = nullptr )
throw_on_missing_variable ( opt_key , " nozzle_diameter " ) ;
return auto_extrusion_width ( opt_key_to_flow_role ( opt_key ) , float ( opt_nozzle_diameters - > get_at ( first_printing_extruder ) ) ) ;
}
return opt - > value ;
}
// Used to provide hints to the user on default extrusion width values, and to provide reasonable values to the PlaceholderParser.
double Flow : : extrusion_width ( const std : : string & opt_key , const ConfigOptionResolver & config , const unsigned int first_printing_extruder )
{
return extrusion_width ( opt_key , config . option < ConfigOptionFloatOrPercent > ( opt_key ) , config , first_printing_extruder ) ;
}
2017-07-19 13:53:43 +00:00
// This constructor builds a Flow object from an extrusion width config setting
// and other context properties.
Flow Flow : : new_from_config_width ( FlowRole role , const ConfigOptionFloatOrPercent & width , float nozzle_diameter , float height , float bridge_flow_ratio )
{
2014-04-05 08:54:24 +00:00
// we need layer height unless it's a bridge
2017-07-19 13:53:43 +00:00
if ( height < = 0 & & bridge_flow_ratio = = 0 )
2020-09-14 14:27:55 +00:00
throw Slic3r : : InvalidArgument ( " Invalid flow height supplied to new_from_config_width() " ) ;
2017-07-19 13:53:43 +00:00
2014-01-05 12:16:13 +00:00
float w ;
2014-06-11 23:00:13 +00:00
if ( bridge_flow_ratio > 0 ) {
2017-07-19 13:53:43 +00:00
// If bridge flow was requested, calculate the bridge width.
height = w = ( bridge_flow_ratio = = 1. ) ?
// optimization to avoid sqrt()
nozzle_diameter :
sqrt ( bridge_flow_ratio ) * nozzle_diameter ;
} else if ( ! width . percent & & width . value = = 0. ) {
// If user left option to 0, calculate a sane default width.
2020-02-04 13:43:58 +00:00
w = auto_extrusion_width ( role , nozzle_diameter ) ;
2014-01-05 12:16:13 +00:00
} else {
2017-07-19 13:53:43 +00:00
// If user set a manual value, use it.
2017-07-19 14:06:29 +00:00
w = float ( width . get_abs_value ( height ) ) ;
2014-01-05 12:16:13 +00:00
}
2014-06-11 23:00:13 +00:00
return Flow ( w , height , nozzle_diameter , bridge_flow_ratio > 0 ) ;
2014-01-05 12:16:13 +00:00
}
2017-07-19 13:53:43 +00:00
// This constructor builds a Flow object from a given centerline spacing.
Flow Flow : : new_from_spacing ( float spacing , float nozzle_diameter , float height , bool bridge )
{
2014-04-05 08:54:24 +00:00
// we need layer height unless it's a bridge
2017-07-19 13:53:43 +00:00
if ( height < = 0 & & ! bridge )
2020-09-14 14:27:55 +00:00
throw Slic3r : : InvalidArgument ( " Invalid flow height supplied to new_from_spacing() " ) ;
2017-07-19 13:53:43 +00:00
// Calculate width from spacing.
// For normal extrusons, extrusion width is wider than the spacing due to the rounding and squishing of the extrusions.
// For bridge extrusions, the extrusions are placed with a tiny BRIDGE_EXTRA_SPACING gaps between the threads.
2017-07-19 14:06:29 +00:00
float width = float ( bridge ?
2017-07-19 13:53:43 +00:00
( spacing - BRIDGE_EXTRA_SPACING ) :
# ifdef HAS_PERIMETER_LINE_OVERLAP
( spacing + PERIMETER_LINE_OVERLAP_FACTOR * height * ( 1. - 0.25 * PI ) ) ;
# else
2017-07-19 14:06:29 +00:00
( spacing + height * ( 1. - 0.25 * PI ) ) ) ;
2017-07-19 13:53:43 +00:00
# endif
return Flow ( width , bridge ? width : height , nozzle_diameter , bridge ) ;
2014-06-11 23:00:13 +00:00
}
2017-07-19 13:53:43 +00:00
// This method returns the centerline spacing between two adjacent extrusions
// having the same extrusion width (and other properties).
float Flow : : spacing ( ) const
2016-11-23 14:51:47 +00:00
{
# ifdef HAS_PERIMETER_LINE_OVERLAP
if ( this - > bridge )
2014-06-12 07:15:40 +00:00
return this - > width + BRIDGE_EXTRA_SPACING ;
2014-07-26 13:29:24 +00:00
// rectangle with semicircles at the ends
2017-07-19 13:53:43 +00:00
float min_flow_spacing = this - > width - this - > height * ( 1. - 0.25 * PI ) ;
2019-08-06 13:11:46 +00:00
float res = this - > width - PERIMETER_LINE_OVERLAP_FACTOR * ( this - > width - min_flow_spacing ) ;
2016-11-23 14:51:47 +00:00
# else
2019-08-06 13:11:46 +00:00
float res = float ( this - > bridge ? ( this - > width + BRIDGE_EXTRA_SPACING ) : ( this - > width - this - > height * ( 1. - 0.25 * PI ) ) ) ;
2016-11-23 14:51:47 +00:00
# endif
2019-08-06 13:11:46 +00:00
// assert(res > 0.f);
if ( res < = 0.f )
2020-02-18 11:26:48 +00:00
throw FlowErrorNegativeSpacing ( ) ;
2019-08-06 13:11:46 +00:00
return res ;
2014-01-05 12:16:13 +00:00
}
2017-07-19 13:53:43 +00:00
// This method returns the centerline spacing between an extrusion using this
// flow and another one using another flow.
// this->spacing(other) shall return the same value as other.spacing(*this)
float Flow : : spacing ( const Flow & other ) const
{
2014-06-12 07:15:40 +00:00
assert ( this - > height = = other . height ) ;
assert ( this - > bridge = = other . bridge ) ;
2019-08-06 13:11:46 +00:00
float res = float ( this - > bridge ?
2017-07-19 14:06:29 +00:00
0.5 * this - > width + 0.5 * other . width + BRIDGE_EXTRA_SPACING :
0.5 * this - > spacing ( ) + 0.5 * other . spacing ( ) ) ;
2019-08-06 13:11:46 +00:00
// assert(res > 0.f);
if ( res < = 0.f )
2020-02-18 11:26:48 +00:00
throw FlowErrorNegativeSpacing ( ) ;
2019-08-06 13:11:46 +00:00
return res ;
2014-06-12 07:15:40 +00:00
}
2017-07-19 13:53:43 +00:00
// This method returns extrusion volume per head move unit.
2016-11-23 14:51:47 +00:00
double Flow : : mm3_per_mm ( ) const
{
2019-08-06 13:11:46 +00:00
float res = this - > bridge ?
2017-09-12 16:20:06 +00:00
// Area of a circle with dmr of this->width.
2020-01-14 10:54:09 +00:00
float ( ( this - > width * this - > width ) * 0.25 * PI ) :
2017-09-12 16:20:06 +00:00
// Rectangle with semicircles at the ends. ~ h (w - 0.215 h)
2020-01-14 10:54:09 +00:00
float ( this - > height * ( this - > width - this - > height * ( 1. - 0.25 * PI ) ) ) ;
2019-08-06 13:11:46 +00:00
//assert(res > 0.);
if ( res < = 0. )
2020-02-18 11:26:48 +00:00
throw FlowErrorNegativeFlow ( ) ;
2017-09-12 16:20:06 +00:00
return res ;
2014-01-05 12:16:13 +00:00
}
2017-05-03 16:28:22 +00:00
Flow support_material_flow ( const PrintObject * object , float layer_height )
{
return Flow : : new_from_config_width (
frSupportMaterial ,
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
2018-09-11 12:04:47 +00:00
( object - > config ( ) . support_material_extrusion_width . value > 0 ) ? object - > config ( ) . support_material_extrusion_width : object - > config ( ) . extrusion_width ,
// if object->config().support_material_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
float ( object - > print ( ) - > config ( ) . nozzle_diameter . get_at ( object - > config ( ) . support_material_extruder - 1 ) ) ,
( layer_height > 0.f ) ? layer_height : float ( object - > config ( ) . layer_height . value ) ,
2018-09-17 13:12:13 +00:00
// bridge_flow_ratio
0.f ) ;
2017-05-03 16:28:22 +00:00
}
Flow support_material_1st_layer_flow ( const PrintObject * object , float layer_height )
{
2018-09-11 12:04:47 +00:00
const auto & width = ( object - > print ( ) - > config ( ) . first_layer_extrusion_width . value > 0 ) ? object - > print ( ) - > config ( ) . first_layer_extrusion_width : object - > config ( ) . support_material_extrusion_width ;
2017-05-03 16:28:22 +00:00
return Flow : : new_from_config_width (
frSupportMaterial ,
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
2018-09-11 12:04:47 +00:00
( width . value > 0 ) ? width : object - > config ( ) . extrusion_width ,
float ( object - > print ( ) - > config ( ) . nozzle_diameter . get_at ( object - > config ( ) . support_material_extruder - 1 ) ) ,
( layer_height > 0.f ) ? layer_height : float ( object - > config ( ) . first_layer_height . get_abs_value ( object - > config ( ) . layer_height . value ) ) ,
2018-09-17 13:12:13 +00:00
// bridge_flow_ratio
0.f ) ;
2017-05-03 16:28:22 +00:00
}
Flow support_material_interface_flow ( const PrintObject * object , float layer_height )
{
return Flow : : new_from_config_width (
frSupportMaterialInterface ,
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
2018-09-11 12:04:47 +00:00
( object - > config ( ) . support_material_extrusion_width > 0 ) ? object - > config ( ) . support_material_extrusion_width : object - > config ( ) . extrusion_width ,
// if object->config().support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
float ( object - > print ( ) - > config ( ) . nozzle_diameter . get_at ( object - > config ( ) . support_material_interface_extruder - 1 ) ) ,
( layer_height > 0.f ) ? layer_height : float ( object - > config ( ) . layer_height . value ) ,
2018-09-17 13:12:13 +00:00
// bridge_flow_ratio
0.f ) ;
2017-05-03 16:28:22 +00:00
}
2014-01-05 12:16:13 +00:00
}