2014-01-05 13:16:13 +01:00
#include "Flow.hpp"
2020-02-04 14:43:58 +01:00
#include "I18N.hpp"
2017-05-03 18:28:22 +02:00
#include "Print.hpp"
2014-01-05 13:16:13 +01:00
#include <cmath>
2015-12-11 21:36:51 +01:00
#include <assert.h>
2014-01-05 13:16:13 +01:00
2020-02-05 09:57:22 +01:00
#include <boost/algorithm/string/predicate.hpp>
2020-02-04 14:43:58 +01:00
// Mark string for localization and translate.
#define L(s) Slic3r::I18N::translate(s)
2014-01-05 13:16:13 +01:00
namespace Slic3r {
2020-02-18 12:26:48 +01: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 15:53:43 +02:00
// This static method returns a sane extrusion width default.
2020-02-04 14:43:58 +01:00
float Flow :: auto_extrusion_width ( FlowRole role , float nozzle_diameter )
2017-07-19 15:53:43 +02:00
{
switch ( role ) {
case frSupportMaterial :
case frSupportMaterialInterface :
case frTopSolidInfill :
return nozzle_diameter ;
default :
case frExternalPerimeter :
case frPerimeter :
case frSolidInfill :
case frInfill :
2017-07-20 11:03:54 +02:00
return 1.125f * nozzle_diameter ;
2017-07-19 15:53:43 +02:00
}
}
2020-02-04 14:43:58 +01: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 16:27:55 +02:00
throw Slic3r :: RuntimeError ( "opt_key_to_flow_role: invalid argument" );
2020-02-04 14:43:58 +01:00
};
static inline void throw_on_missing_variable ( const std :: string & opt_key , const char * dependent_opt_key )
{
2020-02-18 12:26:48 +01: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 14:43:58 +01: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 15:53:43 +02:00
// This constructor builds a Flow object from an extrusion width config setting
// and other context properties.
2021-03-08 13:44:00 +01:00
Flow Flow :: new_from_config_width ( FlowRole role , const ConfigOptionFloatOrPercent & width , float nozzle_diameter , float height )
2017-07-19 15:53:43 +02:00
{
2021-03-08 13:44:00 +01:00
if ( height <= 0 )
2020-09-14 16:27:55 +02:00
throw Slic3r :: InvalidArgument ( "Invalid flow height supplied to new_from_config_width()" );
2017-07-19 15:53:43 +02:00
2014-01-05 13:16:13 +01:00
float w ;
2021-03-08 13:44:00 +01:00
if ( ! width . percent && width . value == 0. ) {
2017-07-19 15:53:43 +02:00
// If user left option to 0, calculate a sane default width.
2020-02-04 14:43:58 +01:00
w = auto_extrusion_width ( role , nozzle_diameter );
2014-01-05 13:16:13 +01:00
} else {
2017-07-19 15:53:43 +02:00
// If user set a manual value, use it.
2017-07-19 16:06:29 +02:00
w = float ( width . get_abs_value ( height ));
2014-01-05 13:16:13 +01:00
}
2021-03-09 12:30:31 +01:00
return Flow ( w , height , rounded_rectangle_extrusion_spacing ( w , height ), nozzle_diameter , false );
2014-01-05 13:16:13 +01:00
}
2021-03-09 12:30:31 +01:00
// Adjust extrusion flow for new extrusion line spacing, maintaining the old spacing between extrusions.
Flow Flow :: with_spacing ( float new_spacing ) const
2017-07-19 15:53:43 +02:00
{
2021-03-09 12:30:31 +01:00
Flow out = * this ;
if ( m_bridge ) {
// Diameter of the rounded extrusion.
assert ( m_width == m_height );
float gap = m_spacing - m_width ;
auto new_diameter = new_spacing - gap ;
out . m_width = out . m_height = new_diameter ;
} else {
2021-03-09 13:54:42 +01:00
assert ( m_width >= m_height );
2021-03-09 12:30:31 +01:00
out . m_width += new_spacing - m_spacing ;
if ( out . m_width < out . m_height )
throw Slic3r :: InvalidArgument ( "Invalid spacing supplied to Flow::with_spacing()" );
}
out . m_spacing = new_spacing ;
return out ;
2014-01-05 13:16:13 +01:00
}
2021-03-09 12:30:31 +01:00
// Adjust the width / height of a rounded extrusion model to reach the prescribed cross section area while maintaining extrusion spacing.
Flow Flow :: with_cross_section ( float area_new ) const
2016-11-23 15:51:47 +01:00
{
2021-03-09 12:30:31 +01:00
assert ( ! m_bridge );
2021-03-09 13:54:42 +01:00
assert ( m_width >= m_height );
2021-03-09 12:30:31 +01:00
// Adjust for bridge_flow_ratio, maintain the extrusion spacing.
float area = this -> mm3_per_mm ();
if ( area_new > area + EPSILON ) {
// Increasing the flow rate.
float new_full_spacing = area_new / m_height ;
if ( new_full_spacing > m_spacing ) {
// Filling up the spacing without an air gap. Grow the extrusion in height.
float height = area_new / m_spacing ;
return Flow ( rounded_rectangle_extrusion_width_from_spacing ( m_spacing , height ), height , m_spacing , m_nozzle_diameter , false );
} else {
return this -> with_width ( rounded_rectangle_extrusion_width_from_spacing ( area / m_height , m_height ));
}
} else if ( area_new < area - EPSILON ) {
// Decreasing the flow rate.
float width_new = m_width - ( area - area_new ) / m_height ;
2021-03-09 13:54:42 +01:00
assert ( width_new > 0 );
2021-03-09 12:30:31 +01:00
if ( width_new > m_height ) {
// Shrink the extrusion width.
return this -> with_width ( width_new );
} else {
// Create a rounded extrusion.
auto dmr = float ( sqrt ( area_new / M_PI ));
return Flow ( dmr , dmr , m_spacing , m_nozzle_diameter , false );
}
} else
return * this ;
2014-06-12 01:00:13 +02:00
}
2021-03-09 12:30:31 +01:00
float Flow :: rounded_rectangle_extrusion_spacing ( float width , float height )
2017-07-19 15:53:43 +02:00
{
2021-03-09 12:30:31 +01:00
auto out = width - height * float ( 1. - 0.25 * PI );
if ( out <= 0.f )
throw FlowErrorNegativeSpacing ();
return out ;
}
float Flow :: rounded_rectangle_extrusion_width_from_spacing ( float spacing , float height )
{
return float ( spacing + height * ( 1. - 0.25 * PI ));
}
float Flow :: bridge_extrusion_spacing ( float dmr )
{
return dmr + BRIDGE_EXTRA_SPACING ;
2014-06-12 09:15:40 +02:00
}
2017-07-19 15:53:43 +02:00
// This method returns extrusion volume per head move unit.
2021-03-09 12:30:31 +01:00
double Flow :: mm3_per_mm () const
2016-11-23 15:51:47 +01:00
{
2021-03-08 13:44:00 +01:00
float res = m_bridge ?
2017-09-12 18:20:06 +02:00
// Area of a circle with dmr of this->width.
2021-03-08 13:44:00 +01:00
float (( m_width * m_width ) * 0.25 * PI ) :
2017-09-12 18:20:06 +02:00
// Rectangle with semicircles at the ends. ~ h (w - 0.215 h)
2021-03-08 13:44:00 +01:00
float ( m_height * ( m_width - m_height * ( 1. - 0.25 * PI )));
2019-08-06 15:11:46 +02:00
//assert(res > 0.);
if ( res <= 0. )
2020-02-18 12:26:48 +01:00
throw FlowErrorNegativeFlow ();
2017-09-12 18:20:06 +02:00
return res ;
2014-01-05 13:16:13 +01:00
}
2017-05-03 18:28:22 +02: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 14:04:47 +02: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 )),
2021-03-08 13:44:00 +01:00
( layer_height > 0.f ) ? layer_height : float ( object -> config (). layer_height . value ));
2017-05-03 18:28:22 +02:00
}
Flow support_material_1st_layer_flow ( const PrintObject * object , float layer_height )
{
2018-09-11 14:04:47 +02: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 18:28:22 +02: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 14:04:47 +02:00
( width . value > 0 ) ? width : object -> config (). extrusion_width ,
float ( object -> print () -> config (). nozzle_diameter . get_at ( object -> config (). support_material_extruder - 1 )),
2021-03-08 13:44:00 +01:00
( layer_height > 0.f ) ? layer_height : float ( object -> config (). first_layer_height . get_abs_value ( object -> config (). layer_height . value )));
2017-05-03 18:28:22 +02: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 14:04:47 +02: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 )),
2021-03-08 13:44:00 +01:00
( layer_height > 0.f ) ? layer_height : float ( object -> config (). layer_height . value ));
2017-05-03 18:28:22 +02:00
}
2014-01-05 13:16:13 +01:00
}