mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-11-02 20:51:23 -07:00 
			
		
		
		
	WIP: Refactored bridging flow from normal flow, new config value
'thick_bridges' to switch between the Slic3r vs. S3D/Cura/Ideamaker way of printing 1st object layer over supports. Simplified the PresetHints.
This commit is contained in:
		
							parent
							
								
									1569dad5de
								
							
						
					
					
						commit
						ceea9de8b8
					
				
					 21 changed files with 228 additions and 283 deletions
				
			
		| 
						 | 
				
			
			@ -320,7 +320,7 @@ static void make_inner_brim(const Print &print, const ConstPrintObjectPtrs &top_
 | 
			
		|||
    loops = union_pt_chained_outside_in(loops, false);
 | 
			
		||||
    std::reverse(loops.begin(), loops.end());
 | 
			
		||||
    extrusion_entities_append_loops(brim.entities, std::move(loops), erSkirt, float(flow.mm3_per_mm()),
 | 
			
		||||
                                    float(flow.width), float(print.skirt_first_layer_height()));
 | 
			
		||||
                                    float(flow.width()), float(print.skirt_first_layer_height()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Produce brim lines around those objects, that have the brim enabled.
 | 
			
		||||
| 
						 | 
				
			
			@ -495,7 +495,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
 | 
			
		|||
				if (i + 1 == j && first_path.size() > 3 && first_path.front().X == first_path.back().X && first_path.front().Y == first_path.back().Y) {
 | 
			
		||||
					auto *loop = new ExtrusionLoop();
 | 
			
		||||
                    brim.entities.emplace_back(loop);
 | 
			
		||||
					loop->paths.emplace_back(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(print.skirt_first_layer_height()));
 | 
			
		||||
					loop->paths.emplace_back(erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()));
 | 
			
		||||
		            Points &points = loop->paths.front().polyline.points;
 | 
			
		||||
		            points.reserve(first_path.size());
 | 
			
		||||
		            for (const ClipperLib_Z::IntPoint &pt : first_path)
 | 
			
		||||
| 
						 | 
				
			
			@ -506,7 +506,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
 | 
			
		|||
			    	ExtrusionEntityCollection this_loop_trimmed;
 | 
			
		||||
					this_loop_trimmed.entities.reserve(j - i);
 | 
			
		||||
			    	for (; i < j; ++ i) {
 | 
			
		||||
			            this_loop_trimmed.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(print.skirt_first_layer_height())));
 | 
			
		||||
			            this_loop_trimmed.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height())));
 | 
			
		||||
						const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first;
 | 
			
		||||
			            Points &points = static_cast<ExtrusionPath*>(this_loop_trimmed.entities.back())->polyline.points;
 | 
			
		||||
			            points.reserve(path.size());
 | 
			
		||||
| 
						 | 
				
			
			@ -522,7 +522,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
    } else {
 | 
			
		||||
        extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(print.skirt_first_layer_height()));
 | 
			
		||||
        extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    make_inner_brim(print, top_level_objects_with_brim, brim);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -621,7 +621,7 @@ ExPolygon elephant_foot_compensation(const ExPolygon &input_expoly, double min_c
 | 
			
		|||
ExPolygon  elephant_foot_compensation(const ExPolygon  &input, const Flow &external_perimeter_flow, const double compensation)
 | 
			
		||||
{
 | 
			
		||||
    // The contour shall be wide enough to apply the external perimeter plus compensation on both sides.
 | 
			
		||||
    double min_contour_width = double(external_perimeter_flow.width + external_perimeter_flow.spacing());
 | 
			
		||||
    double min_contour_width = double(external_perimeter_flow.width() + external_perimeter_flow.spacing());
 | 
			
		||||
    return elephant_foot_compensation(input, min_contour_width, compensation);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -52,7 +52,9 @@ void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scale
 | 
			
		|||
{
 | 
			
		||||
    // Instantiating the Flow class to get the line spacing.
 | 
			
		||||
    // Don't know the nozzle diameter, setting to zero. It shall not matter it shall be optimized out by the compiler.
 | 
			
		||||
    Flow flow(this->width, this->height, 0.f, is_bridge(this->role()));
 | 
			
		||||
    bool bridge = is_bridge(this->role());
 | 
			
		||||
    assert(! bridge || this->width == this->height);
 | 
			
		||||
    auto flow = bridge ? Flow::bridging_flow(this->width, 0.f) : Flow(this->width, this->height, 0.f);
 | 
			
		||||
    polygons_append(out, offset(this->polyline, 0.5f * float(flow.scaled_spacing()) + scaled_epsilon));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,7 +42,7 @@ struct SurfaceFillParams
 | 
			
		|||
 | 
			
		||||
    // width, height of extrusion, nozzle diameter, is bridge
 | 
			
		||||
    // For the output, for fill generator.
 | 
			
		||||
    Flow 			flow = Flow(0.f, 0.f, 0.f, false);
 | 
			
		||||
    Flow 			flow;
 | 
			
		||||
 | 
			
		||||
	// For the output
 | 
			
		||||
    ExtrusionRole	extrusion_role = ExtrusionRole(0);
 | 
			
		||||
| 
						 | 
				
			
			@ -70,10 +70,10 @@ struct SurfaceFillParams
 | 
			
		|||
//		RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, dont_adjust);
 | 
			
		||||
		RETURN_COMPARE_NON_EQUAL(anchor_length);
 | 
			
		||||
		RETURN_COMPARE_NON_EQUAL(anchor_length_max);
 | 
			
		||||
		RETURN_COMPARE_NON_EQUAL(flow.width);
 | 
			
		||||
		RETURN_COMPARE_NON_EQUAL(flow.height);
 | 
			
		||||
		RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter);
 | 
			
		||||
		RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, flow.bridge);
 | 
			
		||||
		RETURN_COMPARE_NON_EQUAL(flow.width());
 | 
			
		||||
		RETURN_COMPARE_NON_EQUAL(flow.height());
 | 
			
		||||
		RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter());
 | 
			
		||||
		RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, flow.bridge());
 | 
			
		||||
		RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, extrusion_role);
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -143,17 +143,12 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
 | 
			
		|||
		        params.bridge_angle = float(surface.bridge_angle);
 | 
			
		||||
		        params.angle 		= float(Geometry::deg2rad(region_config.fill_angle.value));
 | 
			
		||||
		        
 | 
			
		||||
		        // calculate the actual flow we'll be using for this infill
 | 
			
		||||
		        params.flow = layerm.region()->flow(
 | 
			
		||||
		            extrusion_role,
 | 
			
		||||
		            (surface.thickness == -1) ? layer.height : surface.thickness, 	// extrusion height
 | 
			
		||||
		            is_bridge || Fill::use_bridge_flow(params.pattern), 			// bridge flow?
 | 
			
		||||
		            layer.id() == 0,          										// first layer?
 | 
			
		||||
		            -1,                                 							// auto width
 | 
			
		||||
		            *layer.object()
 | 
			
		||||
		        );
 | 
			
		||||
		        
 | 
			
		||||
		        // Calculate flow spacing for infill pattern generation.
 | 
			
		||||
		        // Calculate the actual flow we'll be using for this infill.
 | 
			
		||||
				params.flow = is_bridge || Fill::use_bridge_flow(params.pattern) ?
 | 
			
		||||
					layerm.bridging_flow(extrusion_role) :
 | 
			
		||||
					layerm.region()->flow(*layer.object(), extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness, layer.id() == 0);
 | 
			
		||||
 | 
			
		||||
				// Calculate flow spacing for infill pattern generation.
 | 
			
		||||
		        if (surface.is_solid() || is_bridge) {
 | 
			
		||||
		            params.spacing = params.flow.spacing();
 | 
			
		||||
		            // Don't limit anchor length for solid or bridging infill.
 | 
			
		||||
| 
						 | 
				
			
			@ -164,14 +159,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
 | 
			
		|||
		            // for all layers, for avoiding the ugly effect of
 | 
			
		||||
		            // misaligned infill on first layer because of different extrusion width and
 | 
			
		||||
		            // layer height
 | 
			
		||||
		            params.spacing = layerm.region()->flow(
 | 
			
		||||
			                frInfill,
 | 
			
		||||
			                layer.object()->config().layer_height.value,  // TODO: handle infill_every_layers?
 | 
			
		||||
			                false,  // no bridge
 | 
			
		||||
			                false,  // no first layer
 | 
			
		||||
			                -1,     // auto width
 | 
			
		||||
			                *layer.object()
 | 
			
		||||
			            ).spacing();
 | 
			
		||||
		            params.spacing = layerm.region()->flow(*layer.object(), frInfill, layer.object()->config().layer_height).spacing();
 | 
			
		||||
		            // Anchor a sparse infill to inner perimeters with the following anchor length:
 | 
			
		||||
			        params.anchor_length = float(region_config.infill_anchor);
 | 
			
		||||
					if (region_config.infill_anchor.percent)
 | 
			
		||||
| 
						 | 
				
			
			@ -278,7 +266,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
 | 
			
		|||
				region_id = region_some_infill;
 | 
			
		||||
			const LayerRegion& layerm = *layer.regions()[region_id];
 | 
			
		||||
	        for (SurfaceFill &surface_fill : surface_fills)
 | 
			
		||||
	        	if (surface_fill.surface.surface_type == stInternalSolid && std::abs(layer.height - surface_fill.params.flow.height) < EPSILON) {
 | 
			
		||||
	        	if (surface_fill.surface.surface_type == stInternalSolid && std::abs(layer.height - surface_fill.params.flow.height()) < EPSILON) {
 | 
			
		||||
	        		internal_solid_fill = &surface_fill;
 | 
			
		||||
	        		break;
 | 
			
		||||
	        	}
 | 
			
		||||
| 
						 | 
				
			
			@ -290,14 +278,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
 | 
			
		|||
		        params.extrusion_role = erInternalInfill;
 | 
			
		||||
		        params.angle 		= float(Geometry::deg2rad(layerm.region()->config().fill_angle.value));
 | 
			
		||||
		        // calculate the actual flow we'll be using for this infill
 | 
			
		||||
		        params.flow = layerm.region()->flow(
 | 
			
		||||
		            frSolidInfill,
 | 
			
		||||
		            layer.height, 		// extrusion height
 | 
			
		||||
		            false, 				// bridge flow?
 | 
			
		||||
		            layer.id() == 0,    // first layer?
 | 
			
		||||
		            -1,                 // auto width
 | 
			
		||||
		            *layer.object()
 | 
			
		||||
		        );
 | 
			
		||||
				params.flow = layerm.region()->flow(*layer.object(), frSolidInfill, layer.height, layer.id() == 0);
 | 
			
		||||
		        params.spacing = params.flow.spacing();	        
 | 
			
		||||
				surface_fills.emplace_back(params);
 | 
			
		||||
				surface_fills.back().surface.surface_type = stInternalSolid;
 | 
			
		||||
| 
						 | 
				
			
			@ -365,9 +346,9 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
 | 
			
		|||
        f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
 | 
			
		||||
 | 
			
		||||
        // calculate flow spacing for infill pattern generation
 | 
			
		||||
        bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge;
 | 
			
		||||
        bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge();
 | 
			
		||||
        double link_max_length = 0.;
 | 
			
		||||
        if (! surface_fill.params.flow.bridge) {
 | 
			
		||||
        if (! surface_fill.params.flow.bridge()) {
 | 
			
		||||
#if 0
 | 
			
		||||
            link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing());
 | 
			
		||||
//            printf("flow spacing: %f,  is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length);
 | 
			
		||||
| 
						 | 
				
			
			@ -380,7 +361,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
 | 
			
		|||
        // Maximum length of the perimeter segment linking two infill lines.
 | 
			
		||||
        f->link_max_length = (coord_t)scale_(link_max_length);
 | 
			
		||||
        // Used by the concentric infill pattern to clip the loops to create extrusion paths.
 | 
			
		||||
        f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
 | 
			
		||||
        f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter()) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
 | 
			
		||||
 | 
			
		||||
        // apply half spacing using this flow's own spacing and generate infill
 | 
			
		||||
        FillParams params;
 | 
			
		||||
| 
						 | 
				
			
			@ -402,15 +383,17 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
 | 
			
		|||
		        // calculate actual flow from spacing (which might have been adjusted by the infill
 | 
			
		||||
		        // pattern generator)
 | 
			
		||||
		        double flow_mm3_per_mm = surface_fill.params.flow.mm3_per_mm();
 | 
			
		||||
		        double flow_width      = surface_fill.params.flow.width;
 | 
			
		||||
		        double flow_width      = surface_fill.params.flow.width();
 | 
			
		||||
		        if (using_internal_flow) {
 | 
			
		||||
		            // if we used the internal flow we're not doing a solid infill
 | 
			
		||||
		            // so we can safely ignore the slight variation that might have
 | 
			
		||||
		            // been applied to f->spacing
 | 
			
		||||
		        } else {
 | 
			
		||||
		            Flow new_flow = Flow::new_from_spacing(float(f->spacing), surface_fill.params.flow.nozzle_diameter, surface_fill.params.flow.height, surface_fill.params.flow.bridge);
 | 
			
		||||
		            Flow new_flow = surface_fill.params.flow.bridge() ?
 | 
			
		||||
		            	Flow::bridging_flow_from_spacing(float(f->spacing), surface_fill.params.flow.nozzle_diameter()) :
 | 
			
		||||
		            	Flow::new_from_spacing(float(f->spacing), surface_fill.params.flow.nozzle_diameter(), surface_fill.params.flow.height());
 | 
			
		||||
		        	flow_mm3_per_mm = new_flow.mm3_per_mm();
 | 
			
		||||
		        	flow_width      = new_flow.width;
 | 
			
		||||
		        	flow_width      = new_flow.width();
 | 
			
		||||
		        }
 | 
			
		||||
		        // Save into layer.
 | 
			
		||||
				ExtrusionEntityCollection* eec = nullptr;
 | 
			
		||||
| 
						 | 
				
			
			@ -420,7 +403,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
 | 
			
		|||
		        extrusion_entities_append_paths(
 | 
			
		||||
		            eec->entities, std::move(polylines),
 | 
			
		||||
		            surface_fill.params.extrusion_role,
 | 
			
		||||
		            flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height);
 | 
			
		||||
		            flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height());
 | 
			
		||||
		    }
 | 
			
		||||
		}
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -619,7 +602,7 @@ void Layer::make_ironing()
 | 
			
		|||
        fill.angle = float(ironing_params.angle + 0.25 * M_PI);
 | 
			
		||||
        fill.link_max_length = (coord_t)scale_(3. * fill.spacing);
 | 
			
		||||
		double height = ironing_params.height * fill.spacing / nozzle_dmr;
 | 
			
		||||
        Flow flow = Flow::new_from_spacing(float(nozzle_dmr), 0., float(height), false);
 | 
			
		||||
        Flow flow = Flow::new_from_spacing(float(nozzle_dmr), 0., float(height));
 | 
			
		||||
        double flow_mm3_per_mm = flow.mm3_per_mm();
 | 
			
		||||
        Surface surface_fill(stTop, ExPolygon());
 | 
			
		||||
        for (ExPolygon &expoly : ironing_areas) {
 | 
			
		||||
| 
						 | 
				
			
			@ -638,7 +621,7 @@ void Layer::make_ironing()
 | 
			
		|||
		        extrusion_entities_append_paths(
 | 
			
		||||
		            eec->entities, std::move(polylines),
 | 
			
		||||
		            erIroning,
 | 
			
		||||
		            flow_mm3_per_mm, float(flow.width), float(height));
 | 
			
		||||
		            flow_mm3_per_mm, float(flow.width()), float(height));
 | 
			
		||||
		    }
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,12 @@
 | 
			
		|||
 | 
			
		||||
#include <boost/algorithm/string/predicate.hpp>
 | 
			
		||||
 | 
			
		||||
// Overlap factor of perimeter lines. Currently no overlap.
 | 
			
		||||
// #define HAS_PERIMETER_LINE_OVERLAP
 | 
			
		||||
#ifdef HAS_PERIMETER_LINE_OVERLAP
 | 
			
		||||
    #define PERIMETER_LINE_OVERLAP_FACTOR 1.0
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// Mark string for localization and translate.
 | 
			
		||||
#define L(s) Slic3r::I18N::translate(s)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -122,20 +128,13 @@ double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionResol
 | 
			
		|||
 | 
			
		||||
// 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)
 | 
			
		||||
Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height)
 | 
			
		||||
{
 | 
			
		||||
    // we need layer height unless it's a bridge
 | 
			
		||||
    if (height <= 0 && bridge_flow_ratio == 0) 
 | 
			
		||||
    if (height <= 0)
 | 
			
		||||
        throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width()");
 | 
			
		||||
 | 
			
		||||
    float w;
 | 
			
		||||
    if (bridge_flow_ratio > 0) {
 | 
			
		||||
        // 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 (! width.percent && width.value == 0.) {
 | 
			
		||||
        // If user left option to 0, calculate a sane default width.
 | 
			
		||||
        w = auto_extrusion_width(role, nozzle_diameter);
 | 
			
		||||
    } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -143,26 +142,23 @@ Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent
 | 
			
		|||
        w = float(width.get_abs_value(height));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return Flow(w, height, nozzle_diameter, bridge_flow_ratio > 0);
 | 
			
		||||
    return Flow(w, height, nozzle_diameter, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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) 
 | 
			
		||||
Flow Flow::new_from_spacing(float spacing, float nozzle_diameter, float height) 
 | 
			
		||||
{
 | 
			
		||||
    // we need layer height unless it's a bridge
 | 
			
		||||
    if (height <= 0 && !bridge) 
 | 
			
		||||
    if (height <= 0) 
 | 
			
		||||
        throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_spacing()");
 | 
			
		||||
    // 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.
 | 
			
		||||
    float width = float(bridge ?
 | 
			
		||||
        (spacing - BRIDGE_EXTRA_SPACING) : 
 | 
			
		||||
    float width = float(
 | 
			
		||||
#ifdef HAS_PERIMETER_LINE_OVERLAP
 | 
			
		||||
        (spacing + PERIMETER_LINE_OVERLAP_FACTOR * height * (1. - 0.25 * PI));
 | 
			
		||||
#else
 | 
			
		||||
        (spacing + height * (1. - 0.25 * PI)));
 | 
			
		||||
#endif
 | 
			
		||||
    return Flow(width, bridge ? width : height, nozzle_diameter, bridge);
 | 
			
		||||
    return Flow(width, height, nozzle_diameter);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This method returns the centerline spacing between two adjacent extrusions 
 | 
			
		||||
| 
						 | 
				
			
			@ -170,13 +166,13 @@ Flow Flow::new_from_spacing(float spacing, float nozzle_diameter, float height,
 | 
			
		|||
float Flow::spacing() const 
 | 
			
		||||
{
 | 
			
		||||
#ifdef HAS_PERIMETER_LINE_OVERLAP
 | 
			
		||||
    if (this->bridge)
 | 
			
		||||
        return this->width + BRIDGE_EXTRA_SPACING;
 | 
			
		||||
    if (m_bridge)
 | 
			
		||||
        return m_width + BRIDGE_EXTRA_SPACING;
 | 
			
		||||
    // rectangle with semicircles at the ends
 | 
			
		||||
    float min_flow_spacing = this->width - this->height * (1. - 0.25 * PI);
 | 
			
		||||
    float res = this->width - PERIMETER_LINE_OVERLAP_FACTOR * (this->width - min_flow_spacing);
 | 
			
		||||
    float min_flow_spacing = m_width - m_height * (1. - 0.25 * PI);
 | 
			
		||||
    float res = m_width - PERIMETER_LINE_OVERLAP_FACTOR * (m_width - min_flow_spacing);
 | 
			
		||||
#else
 | 
			
		||||
    float res = float(this->bridge ? (this->width + BRIDGE_EXTRA_SPACING) : (this->width - this->height * (1. - 0.25 * PI)));
 | 
			
		||||
    float res = float(m_bridge ? (m_width + BRIDGE_EXTRA_SPACING) : (m_width - m_height * (1. - 0.25 * PI)));
 | 
			
		||||
#endif
 | 
			
		||||
//    assert(res > 0.f);
 | 
			
		||||
	if (res <= 0.f)
 | 
			
		||||
| 
						 | 
				
			
			@ -189,10 +185,10 @@ float Flow::spacing() const
 | 
			
		|||
// this->spacing(other) shall return the same value as other.spacing(*this)
 | 
			
		||||
float Flow::spacing(const Flow &other) const
 | 
			
		||||
{
 | 
			
		||||
    assert(this->height == other.height);
 | 
			
		||||
    assert(this->bridge == other.bridge);
 | 
			
		||||
    float res = float(this->bridge ? 
 | 
			
		||||
        0.5 * this->width + 0.5 * other.width + BRIDGE_EXTRA_SPACING :
 | 
			
		||||
    assert(m_height == other.m_height);
 | 
			
		||||
    assert(m_bridge == other.m_bridge);
 | 
			
		||||
    float res = float(m_bridge ? 
 | 
			
		||||
        0.5 * m_width + 0.5 * other.m_width + BRIDGE_EXTRA_SPACING :
 | 
			
		||||
        0.5 * this->spacing() + 0.5 * other.spacing());
 | 
			
		||||
//    assert(res > 0.f);
 | 
			
		||||
	if (res <= 0.f)
 | 
			
		||||
| 
						 | 
				
			
			@ -203,11 +199,11 @@ float Flow::spacing(const Flow &other) const
 | 
			
		|||
// This method returns extrusion volume per head move unit.
 | 
			
		||||
double Flow::mm3_per_mm() const 
 | 
			
		||||
{
 | 
			
		||||
    float res = this->bridge ?
 | 
			
		||||
    float res = m_bridge ?
 | 
			
		||||
        // Area of a circle with dmr of this->width.
 | 
			
		||||
        float((this->width * this->width) * 0.25 * PI) :
 | 
			
		||||
        float((m_width * m_width) * 0.25 * PI) :
 | 
			
		||||
        // Rectangle with semicircles at the ends. ~ h (w - 0.215 h)
 | 
			
		||||
        float(this->height * (this->width - this->height * (1. - 0.25 * PI)));
 | 
			
		||||
        float(m_height * (m_width - m_height * (1. - 0.25 * PI)));
 | 
			
		||||
    //assert(res > 0.);
 | 
			
		||||
	if (res <= 0.)
 | 
			
		||||
		throw FlowErrorNegativeFlow();
 | 
			
		||||
| 
						 | 
				
			
			@ -222,9 +218,7 @@ Flow support_material_flow(const PrintObject *object, float layer_height)
 | 
			
		|||
        (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),
 | 
			
		||||
        // bridge_flow_ratio
 | 
			
		||||
        0.f);
 | 
			
		||||
        (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height)
 | 
			
		||||
| 
						 | 
				
			
			@ -235,9 +229,7 @@ Flow support_material_1st_layer_flow(const PrintObject *object, float layer_heig
 | 
			
		|||
        // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
 | 
			
		||||
        (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)),
 | 
			
		||||
        // bridge_flow_ratio
 | 
			
		||||
        0.f);
 | 
			
		||||
        (layer_height > 0.f) ? layer_height : float(object->config().first_layer_height.get_abs_value(object->config().layer_height.value)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Flow support_material_interface_flow(const PrintObject *object, float layer_height)
 | 
			
		||||
| 
						 | 
				
			
			@ -248,9 +240,7 @@ Flow support_material_interface_flow(const PrintObject *object, float layer_heig
 | 
			
		|||
        (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),
 | 
			
		||||
        // bridge_flow_ratio
 | 
			
		||||
        0.f);
 | 
			
		||||
        (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,11 +13,6 @@ class PrintObject;
 | 
			
		|||
// Extra spacing of bridge threads, in mm.
 | 
			
		||||
#define BRIDGE_EXTRA_SPACING 0.05
 | 
			
		||||
 | 
			
		||||
// Overlap factor of perimeter lines. Currently no overlap.
 | 
			
		||||
#ifdef HAS_PERIMETER_LINE_OVERLAP
 | 
			
		||||
    #define PERIMETER_LINE_OVERLAP_FACTOR 1.0
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
enum FlowRole {
 | 
			
		||||
    frExternalPerimeter,
 | 
			
		||||
    frPerimeter,
 | 
			
		||||
| 
						 | 
				
			
			@ -58,22 +53,22 @@ class Flow
 | 
			
		|||
public:
 | 
			
		||||
    // Non bridging flow: Maximum width of an extrusion with semicircles at the ends.
 | 
			
		||||
    // Bridging flow: Bridge thread diameter.
 | 
			
		||||
    float width;
 | 
			
		||||
    float width()           const { return m_width; }
 | 
			
		||||
    // Non bridging flow: Layer height.
 | 
			
		||||
    // Bridging flow: Bridge thread diameter = layer height.
 | 
			
		||||
    float height;
 | 
			
		||||
    float height()          const { return m_height; }
 | 
			
		||||
    // Nozzle diameter. 
 | 
			
		||||
    float nozzle_diameter;
 | 
			
		||||
    float nozzle_diameter() const { return m_nozzle_diameter; }
 | 
			
		||||
    // Is it a bridge?
 | 
			
		||||
    bool  bridge;
 | 
			
		||||
    bool  bridge()          const { return m_bridge; }
 | 
			
		||||
    
 | 
			
		||||
    Flow(float _w, float _h, float _nd, bool _bridge = false) :
 | 
			
		||||
        width(_w), height(_h), nozzle_diameter(_nd), bridge(_bridge) {}
 | 
			
		||||
    Flow() = default;
 | 
			
		||||
    Flow(float w, float h, float nozzle_diameter) : Flow(w, h, nozzle_diameter, false) {}
 | 
			
		||||
 | 
			
		||||
    float   spacing() const;
 | 
			
		||||
    float   spacing(const Flow &other) const;
 | 
			
		||||
    double  mm3_per_mm() const;
 | 
			
		||||
    coord_t scaled_width() const { return coord_t(scale_(this->width)); }
 | 
			
		||||
    coord_t scaled_width() const { return coord_t(scale_(m_width)); }
 | 
			
		||||
    coord_t scaled_spacing() const { return coord_t(scale_(this->spacing())); }
 | 
			
		||||
    coord_t scaled_spacing(const Flow &other) const { return coord_t(scale_(this->spacing(other))); }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -83,13 +78,20 @@ public:
 | 
			
		|||
    // Here an overlap of 0.2x external perimeter spacing is allowed for by the elephant foot compensation.
 | 
			
		||||
    coord_t scaled_elephant_foot_spacing() const { return coord_t(0.5f * float(this->scaled_width() + 0.6f * this->scaled_spacing())); }
 | 
			
		||||
 | 
			
		||||
    bool operator==(const Flow &rhs) const { return this->width == rhs.width && this->height == rhs.height && this->nozzle_diameter == rhs.nozzle_diameter && this->bridge == rhs.bridge; }
 | 
			
		||||
    bool operator==(const Flow &rhs) const { return m_width == rhs.m_width && m_height == rhs.m_height && m_nozzle_diameter == rhs.m_nozzle_diameter && m_bridge == rhs.m_bridge; }
 | 
			
		||||
 | 
			
		||||
    Flow        with_width (float width)  const { assert(! m_bridge); return Flow(width, m_height, m_nozzle_diameter, m_bridge); }
 | 
			
		||||
    Flow        with_height(float height) const { assert(! m_bridge); return Flow(m_width, height, m_nozzle_diameter, m_bridge); }
 | 
			
		||||
 | 
			
		||||
    static Flow bridging_flow(float dmr, float nozzle_diameter) { return Flow { dmr, dmr, nozzle_diameter, true }; }
 | 
			
		||||
    static Flow bridging_flow_from_spacing(float spacing, float nozzle_diameter) 
 | 
			
		||||
        { auto dmr = spacing - float(BRIDGE_EXTRA_SPACING); return Flow { dmr, dmr, nozzle_diameter, true }; }
 | 
			
		||||
    
 | 
			
		||||
    static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio);
 | 
			
		||||
    static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height);
 | 
			
		||||
    // Create a flow from the spacing of extrusion lines.
 | 
			
		||||
    // This method is used exclusively to calculate new flow of 100% infill, where the extrusion width was allowed to scale
 | 
			
		||||
    // to fit a region with integer number of lines.
 | 
			
		||||
    static Flow new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge);
 | 
			
		||||
    static Flow new_from_spacing(float spacing, float nozzle_diameter, float height);
 | 
			
		||||
 | 
			
		||||
    // Sane extrusion width defautl based on nozzle diameter.
 | 
			
		||||
    // The defaults were derived from manual Prusa MK3 profiles.
 | 
			
		||||
| 
						 | 
				
			
			@ -100,6 +102,14 @@ public:
 | 
			
		|||
    // on active extruder etc. Therefore the value calculated by this function shall be used as a hint only.
 | 
			
		||||
	static double extrusion_width(const std::string &opt_key, const ConfigOptionFloatOrPercent *opt, const ConfigOptionResolver &config, const unsigned int first_printing_extruder = 0);
 | 
			
		||||
	static double extrusion_width(const std::string &opt_key, const ConfigOptionResolver &config, const unsigned int first_printing_extruder = 0);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    Flow(float w, float h, float nozzle_diameter, bool bridge) : m_width(w), m_height(h), m_nozzle_diameter(nozzle_diameter), m_bridge(bridge) {}
 | 
			
		||||
 | 
			
		||||
    float       m_width { 0 };
 | 
			
		||||
    float       m_height { 0 };
 | 
			
		||||
    float       m_nozzle_diameter { 0 };
 | 
			
		||||
    bool        m_bridge { false };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extern Flow support_material_flow(const PrintObject *object, float layer_height = 0.f);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1105,15 +1105,15 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
 | 
			
		|||
    const double       layer_height         = first_object->config().layer_height.value;
 | 
			
		||||
    const double       first_layer_height   = first_object->config().first_layer_height.get_abs_value(layer_height);
 | 
			
		||||
    for (const PrintRegion* region : print.regions()) {
 | 
			
		||||
        _write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(frExternalPerimeter, layer_height, false, false, -1., *first_object).width);
 | 
			
		||||
        _write_format(file, "; perimeters extrusion width = %.2fmm\n",          region->flow(frPerimeter,         layer_height, false, false, -1., *first_object).width);
 | 
			
		||||
        _write_format(file, "; infill extrusion width = %.2fmm\n",              region->flow(frInfill,            layer_height, false, false, -1., *first_object).width);
 | 
			
		||||
        _write_format(file, "; solid infill extrusion width = %.2fmm\n",        region->flow(frSolidInfill,       layer_height, false, false, -1., *first_object).width);
 | 
			
		||||
        _write_format(file, "; top infill extrusion width = %.2fmm\n",          region->flow(frTopSolidInfill,    layer_height, false, false, -1., *first_object).width);
 | 
			
		||||
        _write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(*first_object, frExternalPerimeter, layer_height).width());
 | 
			
		||||
        _write_format(file, "; perimeters extrusion width = %.2fmm\n",          region->flow(*first_object, frPerimeter,         layer_height).width());
 | 
			
		||||
        _write_format(file, "; infill extrusion width = %.2fmm\n",              region->flow(*first_object, frInfill,            layer_height).width());
 | 
			
		||||
        _write_format(file, "; solid infill extrusion width = %.2fmm\n",        region->flow(*first_object, frSolidInfill,       layer_height).width());
 | 
			
		||||
        _write_format(file, "; top infill extrusion width = %.2fmm\n",          region->flow(*first_object, frTopSolidInfill,    layer_height).width());
 | 
			
		||||
        if (print.has_support_material())
 | 
			
		||||
            _write_format(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width);
 | 
			
		||||
            _write_format(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width());
 | 
			
		||||
        if (print.config().first_layer_extrusion_width.value > 0)
 | 
			
		||||
            _write_format(file, "; first layer extrusion width = %.2fmm\n",   region->flow(frPerimeter, first_layer_height, false, true, -1., *first_object).width);
 | 
			
		||||
            _write_format(file, "; first layer extrusion width = %.2fmm\n",   region->flow(*first_object, frPerimeter, first_layer_height, true).width());
 | 
			
		||||
        _write_format(file, "\n");
 | 
			
		||||
    }
 | 
			
		||||
    print.throw_if_canceled();
 | 
			
		||||
| 
						 | 
				
			
			@ -2170,14 +2170,13 @@ void GCode::process_layer(
 | 
			
		|||
            const std::pair<size_t, size_t> loops = loops_it->second;
 | 
			
		||||
            this->set_origin(0., 0.);
 | 
			
		||||
            m_avoid_crossing_perimeters.use_external_mp();
 | 
			
		||||
            Flow layer_skirt_flow(print.skirt_flow());
 | 
			
		||||
            layer_skirt_flow.height = float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2]));
 | 
			
		||||
            Flow layer_skirt_flow = print.skirt_flow().with_height(float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2])));
 | 
			
		||||
            double mm3_per_mm = layer_skirt_flow.mm3_per_mm();
 | 
			
		||||
            for (size_t i = loops.first; i < loops.second; ++i) {
 | 
			
		||||
                // Adjust flow according to this layer's layer height.
 | 
			
		||||
                ExtrusionLoop loop = *dynamic_cast<const ExtrusionLoop*>(print.skirt().entities[i]);
 | 
			
		||||
                for (ExtrusionPath &path : loop.paths) {
 | 
			
		||||
                    path.height = layer_skirt_flow.height;
 | 
			
		||||
                    path.height = layer_skirt_flow.height();
 | 
			
		||||
                    path.mm3_per_mm = mm3_per_mm;
 | 
			
		||||
                }
 | 
			
		||||
                //FIXME using the support_material_speed of the 1st object printed.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,7 +59,9 @@ public:
 | 
			
		|||
    // (this collection contains only ExtrusionEntityCollection objects)
 | 
			
		||||
    ExtrusionEntityCollection   fills;
 | 
			
		||||
    
 | 
			
		||||
    Flow    flow(FlowRole role, bool bridge = false, double width = -1) const;
 | 
			
		||||
    Flow    flow(FlowRole role) const;
 | 
			
		||||
    Flow    bridging_flow(FlowRole role) const;
 | 
			
		||||
 | 
			
		||||
    void    slices_to_fill_surfaces_clipped();
 | 
			
		||||
    void    prepare_fill_surfaces();
 | 
			
		||||
    void    make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,16 +15,14 @@
 | 
			
		|||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const
 | 
			
		||||
Flow LayerRegion::flow(FlowRole role) const
 | 
			
		||||
{
 | 
			
		||||
    return m_region->flow(
 | 
			
		||||
        role,
 | 
			
		||||
        m_layer->height,
 | 
			
		||||
        bridge,
 | 
			
		||||
        m_layer->id() == 0,
 | 
			
		||||
        width,
 | 
			
		||||
        *m_layer->object()
 | 
			
		||||
    );
 | 
			
		||||
    return m_region->flow(*m_layer->object(), role, m_layer->height, m_layer->id() == 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Flow LayerRegion::bridging_flow(FlowRole role) const
 | 
			
		||||
{ 
 | 
			
		||||
    return this->layer()->object()->config().thick_bridges ? m_region->bridging_flow(role) : this->flow(role);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces.
 | 
			
		||||
| 
						 | 
				
			
			@ -84,7 +82,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec
 | 
			
		|||
    
 | 
			
		||||
    g.layer_id              = (int)this->layer()->id();
 | 
			
		||||
    g.ext_perimeter_flow    = this->flow(frExternalPerimeter);
 | 
			
		||||
    g.overhang_flow         = this->region()->flow(frPerimeter, -1, true, false, -1, *this->layer()->object());
 | 
			
		||||
    g.overhang_flow         = this->bridging_flow(frPerimeter);
 | 
			
		||||
    g.solid_infill_flow     = this->flow(frSolidInfill);
 | 
			
		||||
    
 | 
			
		||||
    g.process();
 | 
			
		||||
| 
						 | 
				
			
			@ -266,11 +264,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
 | 
			
		|||
                // would get merged into a single one while they need different directions
 | 
			
		||||
                // also, supply the original expolygon instead of the grown one, because in case
 | 
			
		||||
                // of very thin (but still working) anchors, the grown expolygon would go beyond them
 | 
			
		||||
                BridgeDetector bd(
 | 
			
		||||
                    initial,
 | 
			
		||||
                    lower_layer->lslices,
 | 
			
		||||
                    this->flow(frInfill, true).scaled_width()
 | 
			
		||||
                );
 | 
			
		||||
                BridgeDetector bd(initial, lower_layer->lslices, this->bridging_flow(frInfill).scaled_width());
 | 
			
		||||
                #ifdef SLIC3R_DEBUG
 | 
			
		||||
                printf("Processing bridge at layer %zu:\n", this->layer()->id());
 | 
			
		||||
                #endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,7 @@
 | 
			
		|||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, Flow &flow, const float tolerance)
 | 
			
		||||
static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance)
 | 
			
		||||
{
 | 
			
		||||
    ExtrusionPaths paths;
 | 
			
		||||
    ExtrusionPath path(role);
 | 
			
		||||
| 
						 | 
				
			
			@ -62,15 +62,15 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thi
 | 
			
		|||
            path.polyline.append(line.b);
 | 
			
		||||
            // Convert from spacing to extrusion width based on the extrusion model
 | 
			
		||||
            // of a square extrusion ended with semi circles.
 | 
			
		||||
            flow.width = unscale<float>(w) + flow.height * float(1. - 0.25 * PI);
 | 
			
		||||
            Flow new_flow = flow.with_width(unscale<float>(w) + flow.height() * float(1. - 0.25 * PI));
 | 
			
		||||
            #ifdef SLIC3R_DEBUG
 | 
			
		||||
            printf("  filling %f gap\n", flow.width);
 | 
			
		||||
            #endif
 | 
			
		||||
            path.mm3_per_mm  = flow.mm3_per_mm();
 | 
			
		||||
            path.width       = flow.width;
 | 
			
		||||
            path.height      = flow.height;
 | 
			
		||||
            path.mm3_per_mm  = new_flow.mm3_per_mm();
 | 
			
		||||
            path.width       = new_flow.width();
 | 
			
		||||
            path.height      = new_flow.height();
 | 
			
		||||
        } else {
 | 
			
		||||
            thickness_delta = fabs(scale_(flow.width) - w);
 | 
			
		||||
            thickness_delta = fabs(scale_(flow.width()) - w);
 | 
			
		||||
            if (thickness_delta <= tolerance) {
 | 
			
		||||
                // the width difference between this line and the current flow width is 
 | 
			
		||||
                // within the accepted tolerance
 | 
			
		||||
| 
						 | 
				
			
			@ -88,7 +88,7 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thi
 | 
			
		|||
    return paths;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void variable_width(const ThickPolylines& polylines, ExtrusionRole role, Flow flow, std::vector<ExtrusionEntity*> &out)
 | 
			
		||||
static void variable_width(const ThickPolylines& polylines, ExtrusionRole role, const Flow &flow, std::vector<ExtrusionEntity*> &out)
 | 
			
		||||
{
 | 
			
		||||
	// This value determines granularity of adaptive width, as G-code does not allow
 | 
			
		||||
	// variable extrusion within a single move; this value shall only affect the amount
 | 
			
		||||
| 
						 | 
				
			
			@ -205,8 +205,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
 | 
			
		|||
                paths,
 | 
			
		||||
                intersection_pl({ polygon }, perimeter_generator.lower_slices_polygons()),
 | 
			
		||||
                role,
 | 
			
		||||
                is_external ? perimeter_generator.ext_mm3_per_mm()          : perimeter_generator.mm3_per_mm(),
 | 
			
		||||
                is_external ? perimeter_generator.ext_perimeter_flow.width  : perimeter_generator.perimeter_flow.width,
 | 
			
		||||
                is_external ? perimeter_generator.ext_mm3_per_mm()           : perimeter_generator.mm3_per_mm(),
 | 
			
		||||
                is_external ? perimeter_generator.ext_perimeter_flow.width() : perimeter_generator.perimeter_flow.width(),
 | 
			
		||||
                (float)perimeter_generator.layer_height);
 | 
			
		||||
            
 | 
			
		||||
            // get overhang paths by checking what parts of this loop fall 
 | 
			
		||||
| 
						 | 
				
			
			@ -217,8 +217,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
 | 
			
		|||
                diff_pl({ polygon }, perimeter_generator.lower_slices_polygons()),
 | 
			
		||||
                erOverhangPerimeter,
 | 
			
		||||
                perimeter_generator.mm3_per_mm_overhang(),
 | 
			
		||||
                perimeter_generator.overhang_flow.width,
 | 
			
		||||
                perimeter_generator.overhang_flow.height);
 | 
			
		||||
                perimeter_generator.overhang_flow.width(),
 | 
			
		||||
                perimeter_generator.overhang_flow.height());
 | 
			
		||||
            
 | 
			
		||||
            // Reapply the nearest point search for starting point.
 | 
			
		||||
            // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping.
 | 
			
		||||
| 
						 | 
				
			
			@ -226,8 +226,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
 | 
			
		|||
        } else {
 | 
			
		||||
            ExtrusionPath path(role);
 | 
			
		||||
            path.polyline   = polygon.split_at_first_point();
 | 
			
		||||
            path.mm3_per_mm = is_external ? perimeter_generator.ext_mm3_per_mm()          : perimeter_generator.mm3_per_mm();
 | 
			
		||||
            path.width      = is_external ? perimeter_generator.ext_perimeter_flow.width  : perimeter_generator.perimeter_flow.width;
 | 
			
		||||
            path.mm3_per_mm = is_external ? perimeter_generator.ext_mm3_per_mm()           : perimeter_generator.mm3_per_mm();
 | 
			
		||||
            path.width      = is_external ? perimeter_generator.ext_perimeter_flow.width() : perimeter_generator.perimeter_flow.width();
 | 
			
		||||
            path.height     = (float)perimeter_generator.layer_height;
 | 
			
		||||
            paths.push_back(path);
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -346,7 +346,7 @@ void PerimeterGenerator::process()
 | 
			
		|||
                    if (this->config->thin_walls) {
 | 
			
		||||
                        // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
 | 
			
		||||
                        // (actually, something larger than that still may exist due to mitering or other causes)
 | 
			
		||||
                        coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter / 3));
 | 
			
		||||
                        coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter() / 3));
 | 
			
		||||
                        ExPolygons expp = offset2_ex(
 | 
			
		||||
                            // medial axis requires non-overlapping geometry
 | 
			
		||||
                            diff_ex(to_polygons(last),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -430,7 +430,7 @@ const std::vector<std::string>& Preset::print_options()
 | 
			
		|||
        "support_material_pattern", "support_material_with_sheath", "support_material_spacing",
 | 
			
		||||
        "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers",
 | 
			
		||||
        "support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance",
 | 
			
		||||
        "support_material_buildplate_only", "dont_support_bridges", "notes", "complete_objects", "extruder_clearance_radius",
 | 
			
		||||
        "support_material_buildplate_only", "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius",
 | 
			
		||||
        "extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "perimeter_extruder",
 | 
			
		||||
        "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder",
 | 
			
		||||
        "ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1577,9 +1577,7 @@ Flow Print::brim_flow() const
 | 
			
		|||
        frPerimeter,
 | 
			
		||||
		width,
 | 
			
		||||
        (float)m_config.nozzle_diameter.get_at(m_regions.front()->config().perimeter_extruder-1),
 | 
			
		||||
		(float)this->skirt_first_layer_height(),
 | 
			
		||||
        0
 | 
			
		||||
    );
 | 
			
		||||
		(float)this->skirt_first_layer_height());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Flow Print::skirt_flow() const
 | 
			
		||||
| 
						 | 
				
			
			@ -1599,9 +1597,7 @@ Flow Print::skirt_flow() const
 | 
			
		|||
        frPerimeter,
 | 
			
		||||
		width,
 | 
			
		||||
		(float)m_config.nozzle_diameter.get_at(m_objects.front()->config().support_material_extruder-1),
 | 
			
		||||
		(float)this->skirt_first_layer_height(),
 | 
			
		||||
        0
 | 
			
		||||
    );
 | 
			
		||||
		(float)this->skirt_first_layer_height());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Print::has_support_material() const
 | 
			
		||||
| 
						 | 
				
			
			@ -1818,7 +1814,7 @@ void Print::_make_skirt()
 | 
			
		|||
            ExtrusionPath(
 | 
			
		||||
                erSkirt,
 | 
			
		||||
                (float)mm3_per_mm,         // this will be overridden at G-code export time
 | 
			
		||||
                flow.width,
 | 
			
		||||
                flow.width(),
 | 
			
		||||
				(float)first_layer_height  // this will be overridden at G-code export time
 | 
			
		||||
            )));
 | 
			
		||||
        eloop.paths.back().polyline = loop.split_at_first_point();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -65,7 +65,8 @@ public:
 | 
			
		|||
    const PrintRegionConfig&    config() const { return m_config; }
 | 
			
		||||
	// 1-based extruder identifier for this region and role.
 | 
			
		||||
	unsigned int 				extruder(FlowRole role) const;
 | 
			
		||||
    Flow                        flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const;
 | 
			
		||||
    Flow                        flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer = false) const;
 | 
			
		||||
    Flow                        bridging_flow(FlowRole role) const;
 | 
			
		||||
    // Average diameter of nozzles participating on extruding this region.
 | 
			
		||||
    coordf_t                    nozzle_dmr_avg(const PrintConfig &print_config) const;
 | 
			
		||||
    // Average diameter of nozzles participating on extruding this region.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2415,6 +2415,13 @@ void PrintConfigDef::init_fff_params()
 | 
			
		|||
    def->max = max_temp;
 | 
			
		||||
    def->set_default_value(new ConfigOptionInts { 200 });
 | 
			
		||||
 | 
			
		||||
    def = this->add("thick_bridges", coBool);
 | 
			
		||||
    def->label = L("Thick bridges");
 | 
			
		||||
    def->category = L("Layers and Perimeters");
 | 
			
		||||
    def->tooltip = L("Print bridges with round extrusions.");
 | 
			
		||||
    def->mode = comAdvanced;
 | 
			
		||||
    def->set_default_value(new ConfigOptionBool(true));
 | 
			
		||||
 | 
			
		||||
    def = this->add("thin_walls", coBool);
 | 
			
		||||
    def->label = L("Detect thin walls");
 | 
			
		||||
    def->category = L("Layers and Perimeters");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -522,6 +522,7 @@ public:
 | 
			
		|||
    ConfigOptionInt                 support_material_threshold;
 | 
			
		||||
    ConfigOptionBool                support_material_with_sheath;
 | 
			
		||||
    ConfigOptionFloatOrPercent      support_material_xy_spacing;
 | 
			
		||||
    ConfigOptionBool                thick_bridges;
 | 
			
		||||
    ConfigOptionFloat               xy_size_compensation;
 | 
			
		||||
    ConfigOptionBool                wipe_into_objects;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -569,6 +570,7 @@ protected:
 | 
			
		|||
        OPT_PTR(support_material_xy_spacing);
 | 
			
		||||
        OPT_PTR(support_material_threshold);
 | 
			
		||||
        OPT_PTR(support_material_with_sheath);
 | 
			
		||||
        OPT_PTR(thick_bridges);
 | 
			
		||||
        OPT_PTR(xy_size_compensation);
 | 
			
		||||
        OPT_PTR(wipe_into_objects);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -554,6 +554,7 @@ bool PrintObject::invalidate_state_by_config_options(
 | 
			
		|||
            || opt_key == "perimeter_extrusion_width"
 | 
			
		||||
            || opt_key == "infill_overlap"
 | 
			
		||||
            || opt_key == "thin_walls"
 | 
			
		||||
            || opt_key == "thick_bridges"
 | 
			
		||||
            || opt_key == "external_perimeters_first") {
 | 
			
		||||
            steps.emplace_back(posPerimeters);
 | 
			
		||||
        } else if (
 | 
			
		||||
| 
						 | 
				
			
			@ -1459,14 +1460,7 @@ void PrintObject::bridge_over_infill()
 | 
			
		|||
        if (region.config().fill_density.value == 100) continue;
 | 
			
		||||
        
 | 
			
		||||
        // get bridge flow
 | 
			
		||||
        Flow bridge_flow = region.flow(
 | 
			
		||||
            frSolidInfill,
 | 
			
		||||
            -1,     // layer height, not relevant for bridge flow
 | 
			
		||||
            true,   // bridge
 | 
			
		||||
            false,  // first layer
 | 
			
		||||
            -1,     // custom width, not relevant for bridge flow
 | 
			
		||||
            *this
 | 
			
		||||
        );
 | 
			
		||||
        Flow bridge_flow = region.bridging_flow(frSolidInfill);
 | 
			
		||||
        
 | 
			
		||||
		for (LayerPtrs::iterator layer_it = m_layers.begin(); layer_it != m_layers.end(); ++ layer_it) {
 | 
			
		||||
            // skip first layer
 | 
			
		||||
| 
						 | 
				
			
			@ -1488,7 +1482,7 @@ void PrintObject::bridge_over_infill()
 | 
			
		|||
                Polygons to_bridge_pp = internal_solid;
 | 
			
		||||
                
 | 
			
		||||
                // iterate through lower layers spanned by bridge_flow
 | 
			
		||||
                double bottom_z = layer->print_z - bridge_flow.height;
 | 
			
		||||
                double bottom_z = layer->print_z - bridge_flow.height();
 | 
			
		||||
                for (int i = int(layer_it - m_layers.begin()) - 1; i >= 0; --i) {
 | 
			
		||||
                    const Layer* lower_layer = m_layers[i];
 | 
			
		||||
                    
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,31 +18,25 @@ unsigned int PrintRegion::extruder(FlowRole role) const
 | 
			
		|||
    return extruder;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const
 | 
			
		||||
Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer) const
 | 
			
		||||
{
 | 
			
		||||
    ConfigOptionFloatOrPercent config_width;
 | 
			
		||||
    if (width != -1) {
 | 
			
		||||
        // use the supplied custom width, if any
 | 
			
		||||
        config_width.value = width;
 | 
			
		||||
        config_width.percent = false;
 | 
			
		||||
    // Get extrusion width from configuration.
 | 
			
		||||
    // (might be an absolute value, or a percent value, or zero for auto)
 | 
			
		||||
    if (first_layer && m_print->config().first_layer_extrusion_width.value > 0) {
 | 
			
		||||
        config_width = m_print->config().first_layer_extrusion_width;
 | 
			
		||||
    } else if (role == frExternalPerimeter) {
 | 
			
		||||
        config_width = m_config.external_perimeter_extrusion_width;
 | 
			
		||||
    } else if (role == frPerimeter) {
 | 
			
		||||
        config_width = m_config.perimeter_extrusion_width;
 | 
			
		||||
    } else if (role == frInfill) {
 | 
			
		||||
        config_width = m_config.infill_extrusion_width;
 | 
			
		||||
    } else if (role == frSolidInfill) {
 | 
			
		||||
        config_width = m_config.solid_infill_extrusion_width;
 | 
			
		||||
    } else if (role == frTopSolidInfill) {
 | 
			
		||||
        config_width = m_config.top_infill_extrusion_width;
 | 
			
		||||
    } else {
 | 
			
		||||
        // otherwise, get extrusion width from configuration
 | 
			
		||||
        // (might be an absolute value, or a percent value, or zero for auto)
 | 
			
		||||
        if (first_layer && m_print->config().first_layer_extrusion_width.value > 0) {
 | 
			
		||||
            config_width = m_print->config().first_layer_extrusion_width;
 | 
			
		||||
        } else if (role == frExternalPerimeter) {
 | 
			
		||||
            config_width = m_config.external_perimeter_extrusion_width;
 | 
			
		||||
        } else if (role == frPerimeter) {
 | 
			
		||||
            config_width = m_config.perimeter_extrusion_width;
 | 
			
		||||
        } else if (role == frInfill) {
 | 
			
		||||
            config_width = m_config.infill_extrusion_width;
 | 
			
		||||
        } else if (role == frSolidInfill) {
 | 
			
		||||
            config_width = m_config.solid_infill_extrusion_width;
 | 
			
		||||
        } else if (role == frTopSolidInfill) {
 | 
			
		||||
            config_width = m_config.top_infill_extrusion_width;
 | 
			
		||||
        } else {
 | 
			
		||||
            throw Slic3r::InvalidArgument("Unknown role");
 | 
			
		||||
        }
 | 
			
		||||
        throw Slic3r::InvalidArgument("Unknown role");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config_width.value == 0)
 | 
			
		||||
| 
						 | 
				
			
			@ -50,8 +44,19 @@ Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool fir
 | 
			
		|||
    
 | 
			
		||||
    // Get the configured nozzle_diameter for the extruder associated to the flow role requested.
 | 
			
		||||
    // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right.
 | 
			
		||||
    double nozzle_diameter = m_print->config().nozzle_diameter.get_at(this->extruder(role) - 1);
 | 
			
		||||
    return Flow::new_from_config_width(role, config_width, (float)nozzle_diameter, (float)layer_height, bridge ? (float)m_config.bridge_flow_ratio : 0.0f);
 | 
			
		||||
    auto nozzle_diameter = float(m_print->config().nozzle_diameter.get_at(this->extruder(role) - 1));
 | 
			
		||||
    return Flow::new_from_config_width(role, config_width, nozzle_diameter, float(layer_height));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Flow PrintRegion::bridging_flow(FlowRole role) const
 | 
			
		||||
{
 | 
			
		||||
    // Get the configured nozzle_diameter for the extruder associated to the flow role requested.
 | 
			
		||||
    // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right.
 | 
			
		||||
    auto nozzle_diameter = float(m_print->config().nozzle_diameter.get_at(this->extruder(role) - 1));
 | 
			
		||||
    double bfr = m_config.bridge_flow_ratio;
 | 
			
		||||
    if (bfr != 1.)
 | 
			
		||||
        bfr = sqrt(bfr);
 | 
			
		||||
    return Flow::bridging_flow(float(bfr) * nozzle_diameter, nozzle_diameter);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -345,7 +345,7 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object
 | 
			
		|||
    for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id)
 | 
			
		||||
        if (! object->region_volumes[region_id].empty())
 | 
			
		||||
            external_perimeter_width = std::max(external_perimeter_width,
 | 
			
		||||
                (coordf_t)object->print()->get_region(region_id)->flow(frExternalPerimeter, slicing_params.layer_height, false, false, -1, *object).width);
 | 
			
		||||
                (coordf_t)object->print()->get_region(region_id)->flow(*object, frExternalPerimeter, slicing_params.layer_height).width());
 | 
			
		||||
    m_gap_xy = m_object_config->support_material_xy_spacing.get_abs_value(external_perimeter_width);
 | 
			
		||||
 | 
			
		||||
    m_can_merge_support_regions = m_object_config->support_material_extruder.value == m_object_config->support_material_interface_extruder.value;
 | 
			
		||||
| 
						 | 
				
			
			@ -1231,7 +1231,7 @@ namespace SupportMaterialInternal {
 | 
			
		|||
            // since we're dealing with bridges, we can't assume width is larger than spacing,
 | 
			
		||||
            // so we take the largest value and also apply safety offset to be ensure no gaps
 | 
			
		||||
            // are left in between
 | 
			
		||||
            Flow bridge_flow = layerm->flow(frPerimeter, true);
 | 
			
		||||
            Flow bridge_flow = layerm->bridging_flow(frPerimeter);
 | 
			
		||||
            float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing()));
 | 
			
		||||
            for (Polyline &polyline : overhang_perimeters)
 | 
			
		||||
                if (polyline.is_straight()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1542,16 +1542,20 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
 | 
			
		|||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            // Offset the contact polygons outside.
 | 
			
		||||
#if 0
 | 
			
		||||
                            for (size_t i = 0; i < NUM_MARGIN_STEPS; ++ i) {
 | 
			
		||||
                                diff_polygons = diff(
 | 
			
		||||
                                    offset(
 | 
			
		||||
                                        diff_polygons,
 | 
			
		||||
                                        SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS,
 | 
			
		||||
                                        scaled<float>(SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS),
 | 
			
		||||
                                        ClipperLib::jtRound,
 | 
			
		||||
                                        // round mitter limit
 | 
			
		||||
                                        scale_(0.05)),
 | 
			
		||||
                                    slices_margin_cached);
 | 
			
		||||
                            }
 | 
			
		||||
#else
 | 
			
		||||
                            diff_polygons = diff(diff_polygons, slices_margin_cached);
 | 
			
		||||
#endif
 | 
			
		||||
                        }
 | 
			
		||||
                        polygons_append(contact_polygons, diff_polygons);
 | 
			
		||||
                    } // for each layer.region
 | 
			
		||||
| 
						 | 
				
			
			@ -1597,7 +1601,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
 | 
			
		|||
 | 
			
		||||
                        // Contact layer will be printed with a normal flow, but
 | 
			
		||||
                        // it will support layers printed with a bridging flow.
 | 
			
		||||
                        if (SupportMaterialInternal::has_bridging_extrusions(layer)) {
 | 
			
		||||
                        if (m_object_config->thick_bridges && SupportMaterialInternal::has_bridging_extrusions(layer)) {
 | 
			
		||||
                            coordf_t bridging_height = 0.;
 | 
			
		||||
                            for (const LayerRegion *region : layer.regions())
 | 
			
		||||
                                bridging_height += region->region()->bridging_height_avg(*m_print_config);
 | 
			
		||||
| 
						 | 
				
			
			@ -1879,12 +1883,14 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
 | 
			
		|||
                                //FIXME Check whether the bottom bridging surfaces are extruded correctly (no bridging flow correction applied?)
 | 
			
		||||
                                // According to Jindrich the bottom surfaces work well.
 | 
			
		||||
                                //FIXME test the bridging flow instead?
 | 
			
		||||
                                m_support_material_interface_flow.nozzle_diameter;
 | 
			
		||||
                                m_object_config->thick_bridges.value ? m_support_material_interface_flow.nozzle_diameter() :
 | 
			
		||||
                                // Take the default layer height.
 | 
			
		||||
                                m_object_config->layer_height;
 | 
			
		||||
                            layer_new.print_z = m_slicing_params.soluble_interface ? object.layers()[layer_id + 1]->print_z :
 | 
			
		||||
                                layer.print_z + layer_new.height + m_object_config->support_material_contact_distance.value;
 | 
			
		||||
                            layer_new.bottom_z = layer.print_z;
 | 
			
		||||
                            layer_new.idx_object_layer_below = layer_id;
 | 
			
		||||
                            layer_new.bridging = ! m_slicing_params.soluble_interface;
 | 
			
		||||
                            layer_new.bridging = ! m_slicing_params.soluble_interface && m_object_config->thick_bridges;
 | 
			
		||||
                            //FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow.
 | 
			
		||||
                            //FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks.
 | 
			
		||||
                            layer_new.polygons = offset(touching, float(m_support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS);
 | 
			
		||||
| 
						 | 
				
			
			@ -2512,7 +2518,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
 | 
			
		|||
                        break;
 | 
			
		||||
                    polygons_append(polygons_trimming, offset(object_layer.lslices, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
 | 
			
		||||
                }
 | 
			
		||||
                if (! m_slicing_params.soluble_interface) {
 | 
			
		||||
                if (! m_slicing_params.soluble_interface && m_object_config->thick_bridges) {
 | 
			
		||||
                    // Collect all bottom surfaces, which will be extruded with a bridging flow.
 | 
			
		||||
                    for (; i < object.layers().size(); ++ i) {
 | 
			
		||||
                        const Layer &object_layer = *object.layers()[i];
 | 
			
		||||
| 
						 | 
				
			
			@ -2810,7 +2816,7 @@ std::pair<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M
 | 
			
		|||
        // Compress contact_out, remove the nullptr items.
 | 
			
		||||
        remove_nulls(interface_layers);
 | 
			
		||||
        remove_nulls(base_interface_layers);
 | 
			
		||||
        BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start";
 | 
			
		||||
        BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - end";
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return base_and_interface_layers;
 | 
			
		||||
| 
						 | 
				
			
			@ -2835,7 +2841,7 @@ static inline void fill_expolygon_generate_paths(
 | 
			
		|||
        dst,
 | 
			
		||||
        std::move(polylines),
 | 
			
		||||
        role,
 | 
			
		||||
        flow.mm3_per_mm(), flow.width, flow.height);
 | 
			
		||||
        flow.mm3_per_mm(), flow.width(), flow.height());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline void fill_expolygons_generate_paths(
 | 
			
		||||
| 
						 | 
				
			
			@ -2903,7 +2909,7 @@ static inline void fill_expolygons_with_sheath_generate_paths(
 | 
			
		|||
            pl.clip_end(clip_length);
 | 
			
		||||
            polylines.emplace_back(std::move(pl));
 | 
			
		||||
        }
 | 
			
		||||
        extrusion_entities_append_paths(eec->entities, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width, flow.height);
 | 
			
		||||
        extrusion_entities_append_paths(eec->entities, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height());
 | 
			
		||||
        // Fill in the rest.
 | 
			
		||||
        fill_expolygons_generate_paths(eec->entities, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow);
 | 
			
		||||
        dst.emplace_back(eec.release());
 | 
			
		||||
| 
						 | 
				
			
			@ -3011,8 +3017,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const
 | 
			
		|||
    if (n_contact_loops == 0 || top_contact_layer.empty())
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    Flow flow = interface_flow_src;
 | 
			
		||||
    flow.height = float(top_contact_layer.layer->height);
 | 
			
		||||
    Flow flow = interface_flow_src.with_height(top_contact_layer.layer->height);
 | 
			
		||||
 | 
			
		||||
    Polygons overhang_polygons;
 | 
			
		||||
    if (top_contact_layer.layer->overhang_polygons != nullptr)
 | 
			
		||||
| 
						 | 
				
			
			@ -3209,7 +3214,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const
 | 
			
		|||
    extrusion_entities_append_paths(
 | 
			
		||||
        top_contact_layer.extrusions,
 | 
			
		||||
        std::move(loop_lines),
 | 
			
		||||
        erSupportMaterialInterface, flow.mm3_per_mm(), flow.width, flow.height);
 | 
			
		||||
        erSupportMaterialInterface, flow.mm3_per_mm(), flow.width(), flow.height());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef SLIC3R_DEBUG
 | 
			
		||||
| 
						 | 
				
			
			@ -3342,7 +3347,7 @@ void modulate_extrusion_by_overlapping_layers(
 | 
			
		|||
        // Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter).
 | 
			
		||||
        assert(this_layer.print_z > overlapping_layer.print_z);
 | 
			
		||||
        frag.height = float(this_layer.print_z - overlapping_layer.print_z);
 | 
			
		||||
        frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f, false).mm3_per_mm();
 | 
			
		||||
        frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f).mm3_per_mm();
 | 
			
		||||
#ifdef SLIC3R_DEBUG
 | 
			
		||||
        svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1));
 | 
			
		||||
#endif /* SLIC3R_DEBUG */
 | 
			
		||||
| 
						 | 
				
			
			@ -3565,7 +3570,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
 | 
			
		|||
                    //FIXME misusing contact_polygons for support columns.
 | 
			
		||||
                    ((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons);
 | 
			
		||||
                if (! to_infill_polygons.empty()) {
 | 
			
		||||
                    Flow flow(float(m_support_material_flow.width), float(raft_layer.height), m_support_material_flow.nozzle_diameter, raft_layer.bridging);
 | 
			
		||||
                    assert(! raft_layer.bridging);
 | 
			
		||||
                    Flow flow(float(m_support_material_flow.width()), float(raft_layer.height), m_support_material_flow.nozzle_diameter());
 | 
			
		||||
                    Fill * filler = filler_support.get();
 | 
			
		||||
                    filler->angle = raft_angle_base;
 | 
			
		||||
                    filler->spacing = m_support_material_flow.spacing();
 | 
			
		||||
| 
						 | 
				
			
			@ -3596,7 +3602,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
 | 
			
		|||
                // We don't use $base_flow->spacing because we need a constant spacing
 | 
			
		||||
                // value that guarantees that all layers are correctly aligned.
 | 
			
		||||
                filler->spacing = m_support_material_flow.spacing();
 | 
			
		||||
                flow          = Flow(float(m_support_material_interface_flow.width), float(raft_layer.height), m_support_material_flow.nozzle_diameter, raft_layer.bridging);
 | 
			
		||||
                assert(! raft_layer.bridging);
 | 
			
		||||
                flow          = Flow(float(m_support_material_interface_flow.width()), float(raft_layer.height), m_support_material_flow.nozzle_diameter());
 | 
			
		||||
                density       = float(interface_density);
 | 
			
		||||
            } else
 | 
			
		||||
                continue;
 | 
			
		||||
| 
						 | 
				
			
			@ -3734,11 +3741,10 @@ void PrintObjectSupportMaterial::generate_toolpaths(
 | 
			
		|||
                bool interface_as_base = (&layer_ex == &interface_layer) && m_object_config->support_material_interface_layers.value == 0;
 | 
			
		||||
                //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore
 | 
			
		||||
                // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b)
 | 
			
		||||
                Flow interface_flow(
 | 
			
		||||
                    float(layer_ex.layer->bridging ? layer_ex.layer->height : (interface_as_base ? m_support_material_flow.width : m_support_material_interface_flow.width)),
 | 
			
		||||
                    float(layer_ex.layer->height),
 | 
			
		||||
                    m_support_material_interface_flow.nozzle_diameter,
 | 
			
		||||
                    layer_ex.layer->bridging);
 | 
			
		||||
                auto interface_flow = layer_ex.layer->bridging ?
 | 
			
		||||
                    Flow::bridging_flow(layer_ex.layer->height, m_support_material_interface_flow.nozzle_diameter()) :
 | 
			
		||||
                    Flow(interface_as_base ? m_support_material_flow.width() : m_support_material_interface_flow.width(), 
 | 
			
		||||
                        float(layer_ex.layer->height), m_support_material_interface_flow.nozzle_diameter());
 | 
			
		||||
                filler_interface->angle = interface_as_base ?
 | 
			
		||||
                        // If zero interface layers are configured, use the same angle as for the base layers.
 | 
			
		||||
                        angles[support_layer_id % angles.size()] :
 | 
			
		||||
| 
						 | 
				
			
			@ -3762,11 +3768,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
 | 
			
		|||
                Fill *filler = filler_base_interface.get();
 | 
			
		||||
                //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore
 | 
			
		||||
                // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b)
 | 
			
		||||
                Flow interface_flow(
 | 
			
		||||
                    float(base_interface_layer.layer->bridging ? base_interface_layer.layer->height : m_support_material_flow.width), // m_support_material_interface_flow.width)),
 | 
			
		||||
                    float(base_interface_layer.layer->height),
 | 
			
		||||
                    m_support_material_flow.nozzle_diameter,
 | 
			
		||||
                    base_interface_layer.layer->bridging);
 | 
			
		||||
                assert(! base_interface_layer.layer->bridging);
 | 
			
		||||
                Flow interface_flow = m_support_material_flow.with_height(float(base_interface_layer.layer->height));
 | 
			
		||||
                filler->angle   = interface_angle;
 | 
			
		||||
                filler->spacing = m_support_material_interface_flow.spacing();
 | 
			
		||||
                filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / interface_density));
 | 
			
		||||
| 
						 | 
				
			
			@ -3788,11 +3791,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
 | 
			
		|||
                filler->angle = angles[support_layer_id % angles.size()];
 | 
			
		||||
                // We don't use $base_flow->spacing because we need a constant spacing
 | 
			
		||||
                // value that guarantees that all layers are correctly aligned.
 | 
			
		||||
                Flow flow(
 | 
			
		||||
                    float(base_layer.layer->bridging ? base_layer.layer->height : m_support_material_flow.width), 
 | 
			
		||||
                    float(base_layer.layer->height), 
 | 
			
		||||
                    m_support_material_flow.nozzle_diameter, 
 | 
			
		||||
                    base_layer.layer->bridging);
 | 
			
		||||
                assert(! base_layer.layer->bridging);
 | 
			
		||||
                auto flow = m_support_material_flow.with_height(float(base_layer.layer->height));
 | 
			
		||||
                filler->spacing = m_support_material_flow.spacing();
 | 
			
		||||
                filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density));
 | 
			
		||||
                float density = float(support_density);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -148,74 +148,33 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
 | 
			
		|||
                speed_normal = first_layer_speed.get_abs_value(speed_normal);
 | 
			
		||||
            return (speed_normal > 0.) ? speed_normal : speed_max;
 | 
			
		||||
        };
 | 
			
		||||
        auto test_flow =
 | 
			
		||||
            [first_layer_extrusion_width_ptr, extrusion_width, nozzle_diameter, lh, bridging, bridge_speed, bridge_flow_ratio, limit_by_first_layer_speed, max_print_speed, &max_flow, &max_flow_extrusion_type]
 | 
			
		||||
            (FlowRole flow_role, const ConfigOptionFloatOrPercent &this_extrusion_width, double speed, const char *err_msg) {
 | 
			
		||||
            Flow flow = bridging ?
 | 
			
		||||
                Flow::new_from_config_width(flow_role, first_positive(first_layer_extrusion_width_ptr, this_extrusion_width, extrusion_width), nozzle_diameter, lh) :
 | 
			
		||||
                Flow::bridging_flow(nozzle_diameter * bridge_flow_ratio, nozzle_diameter);
 | 
			
		||||
            double volumetric_flow = flow.mm3_per_mm() * (bridging ? bridge_speed : limit_by_first_layer_speed(speed, max_print_speed));
 | 
			
		||||
            if (max_flow < volumetric_flow) {
 | 
			
		||||
                max_flow = volumetric_flow;
 | 
			
		||||
                max_flow_extrusion_type = _utf8(err_msg);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        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 : 
 | 
			
		||||
                    limit_by_first_layer_speed(std::max(external_perimeter_speed, small_perimeter_speed), max_print_speed));
 | 
			
		||||
            if (max_flow < external_perimeter_rate) {
 | 
			
		||||
                max_flow = external_perimeter_rate;
 | 
			
		||||
                max_flow_extrusion_type = _utf8(L("external perimeters"));
 | 
			
		||||
            }
 | 
			
		||||
            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 :
 | 
			
		||||
                    limit_by_first_layer_speed(std::max(perimeter_speed, small_perimeter_speed), max_print_speed));
 | 
			
		||||
            if (max_flow < perimeter_rate) {
 | 
			
		||||
                max_flow = perimeter_rate;
 | 
			
		||||
                max_flow_extrusion_type = _utf8(L("perimeters"));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        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), 
 | 
			
		||||
                nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(infill_speed, max_print_speed);
 | 
			
		||||
            if (max_flow < infill_rate) {
 | 
			
		||||
                max_flow = infill_rate;
 | 
			
		||||
                max_flow_extrusion_type = _utf8(L("infill"));
 | 
			
		||||
            }
 | 
			
		||||
            test_flow(frExternalPerimeter, external_perimeter_extrusion_width, std::max(external_perimeter_speed, small_perimeter_speed), L("external perimeters"));
 | 
			
		||||
            test_flow(frPerimeter,         perimeter_extrusion_width,          std::max(perimeter_speed,          small_perimeter_speed), L("perimeters"));
 | 
			
		||||
        }
 | 
			
		||||
        if (! bridging && infill_extruder_active)
 | 
			
		||||
            test_flow(frInfill, infill_extrusion_width, infill_speed, L("infill"));
 | 
			
		||||
        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() *
 | 
			
		||||
                (bridging ? bridge_speed : limit_by_first_layer_speed(solid_infill_speed, max_print_speed));
 | 
			
		||||
            if (max_flow < solid_infill_rate) {
 | 
			
		||||
                max_flow = solid_infill_rate;
 | 
			
		||||
                max_flow_extrusion_type = _utf8(L("solid infill"));
 | 
			
		||||
            }
 | 
			
		||||
            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), 
 | 
			
		||||
                    nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(top_solid_infill_speed, max_print_speed);
 | 
			
		||||
                if (max_flow < top_solid_infill_rate) {
 | 
			
		||||
                    max_flow = top_solid_infill_rate;
 | 
			
		||||
                    max_flow_extrusion_type = _utf8(L("top solid infill"));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        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() *
 | 
			
		||||
                (bridging ? bridge_speed : limit_by_first_layer_speed(support_material_speed, max_print_speed));
 | 
			
		||||
            if (max_flow < support_material_rate) {
 | 
			
		||||
                max_flow = support_material_rate;
 | 
			
		||||
                max_flow_extrusion_type = _utf8(L("support"));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        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() *
 | 
			
		||||
                (bridging ? bridge_speed : limit_by_first_layer_speed(support_material_interface_speed, max_print_speed));
 | 
			
		||||
            if (max_flow < support_material_interface_rate) {
 | 
			
		||||
                max_flow = support_material_interface_rate;
 | 
			
		||||
                max_flow_extrusion_type = _utf8(L("support interface"));
 | 
			
		||||
            }
 | 
			
		||||
            test_flow(frInfill, solid_infill_extrusion_width, solid_infill_speed, L("solid infill"));
 | 
			
		||||
            if (! bridging)
 | 
			
		||||
                test_flow(frInfill, top_infill_extrusion_width, top_solid_infill_speed, L("top solid infill"));
 | 
			
		||||
        }
 | 
			
		||||
        if (! bridging && support_material_extruder_active)
 | 
			
		||||
            test_flow(frSupportMaterial, support_material_extrusion_width, support_material_speed, L("support"));
 | 
			
		||||
        if (support_material_interface_extruder_active)
 | 
			
		||||
            test_flow(frSupportMaterialInterface, support_material_extrusion_width, support_material_interface_speed, L("support interface"));
 | 
			
		||||
        //FIXME handle gap_fill_speed
 | 
			
		||||
        if (! out.empty())
 | 
			
		||||
            out += "\n";
 | 
			
		||||
| 
						 | 
				
			
			@ -254,11 +213,11 @@ std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle &pre
 | 
			
		|||
    Flow    external_perimeter_flow             = Flow::new_from_config_width(
 | 
			
		||||
        frExternalPerimeter, 
 | 
			
		||||
        *print_config.opt<ConfigOptionFloatOrPercent>("external_perimeter_extrusion_width"), 
 | 
			
		||||
        nozzle_diameter, layer_height, false);
 | 
			
		||||
        nozzle_diameter, layer_height);
 | 
			
		||||
    Flow    perimeter_flow                      = Flow::new_from_config_width(
 | 
			
		||||
        frPerimeter, 
 | 
			
		||||
        *print_config.opt<ConfigOptionFloatOrPercent>("perimeter_extrusion_width"), 
 | 
			
		||||
        nozzle_diameter, layer_height, false);
 | 
			
		||||
        nozzle_diameter, layer_height);
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    if (num_perimeters > 0) {
 | 
			
		||||
| 
						 | 
				
			
			@ -266,7 +225,7 @@ std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle &pre
 | 
			
		|||
        out += (boost::format(_utf8(L("Recommended object thin wall thickness for layer height %.2f and"))) % layer_height).str() + " ";
 | 
			
		||||
        // Start with the width of two closely spaced 
 | 
			
		||||
        try {
 | 
			
		||||
	        double width = external_perimeter_flow.width + external_perimeter_flow.spacing();
 | 
			
		||||
	        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 += ", ";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1433,6 +1433,7 @@ void TabPrint::build()
 | 
			
		|||
        optgroup->append_single_option_line("avoid_crossing_perimeters", category_path + "avoid-crossing-perimeters");
 | 
			
		||||
        optgroup->append_single_option_line("avoid_crossing_perimeters_max_detour", category_path + "avoid_crossing_perimeters_max_detour");
 | 
			
		||||
        optgroup->append_single_option_line("thin_walls", category_path + "detect-thin-walls");
 | 
			
		||||
        optgroup->append_single_option_line("thick_bridges", category_path + "thick_bridges");
 | 
			
		||||
        optgroup->append_single_option_line("overhangs", category_path + "detect-bridging-perimeters");
 | 
			
		||||
 | 
			
		||||
        optgroup = page->new_optgroup(L("Advanced"));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -143,7 +143,7 @@ SCENARIO("Flow: Flow math for non-bridges", "[Flow]") {
 | 
			
		|||
    GIVEN("Input spacing of 0.414159 and a total width of 2") {
 | 
			
		||||
        double in_spacing = 0.414159;
 | 
			
		||||
        double total_width = 2.0;
 | 
			
		||||
        auto flow = Flow::new_from_spacing(1.0, 0.4, 0.3, false);
 | 
			
		||||
        auto flow = Flow::new_from_spacing(1.0, 0.4, 0.3);
 | 
			
		||||
        WHEN("solid_spacing() is called") {
 | 
			
		||||
            double result = flow.solid_spacing(total_width, in_spacing);
 | 
			
		||||
            THEN("Yielded spacing is greater than 0") {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue