Work in progress: Good bye, Perl Threads!

This commit is contained in:
bubnikv 2018-03-23 11:41:20 +01:00
parent 86b79f89ad
commit e931f75010
31 changed files with 833 additions and 1069 deletions

View file

@ -5,11 +5,13 @@
#include "Flow.hpp"
#include "Geometry.hpp"
#include "SupportMaterial.hpp"
#include "GCode.hpp"
#include "GCode/WipeTowerPrusaMM.hpp"
#include <algorithm>
#include <unordered_set>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/log/trivial.hpp>
namespace Slic3r {
@ -793,6 +795,71 @@ void Print::auto_assign_extruders(ModelObject* model_object) const
}
}
// Slicing process, running at a background thread.
void Print::process()
{
BOOST_LOG_TRIVIAL(info) << "Staring the slicing process.";
for (PrintObject *obj : this->objects)
obj->make_perimeters();
this->set_status(70, "Infilling layers");
for (PrintObject *obj : this->objects)
obj->infill();
for (PrintObject *obj : this->objects)
obj->generate_support_material();
if (! this->state.is_done(psSkirt)) {
this->state.set_started(psSkirt);
this->skirt.clear();
if (this->has_skirt()) {
this->set_status(88, "Generating skirt");
this->_make_skirt();
}
this->state.set_done(psSkirt);
}
if (! this->state.is_done(psBrim)) {
this->state.set_started(psBrim);
this->brim.clear();
if (this->config.brim_width > 0) {
this->set_status(88, "Generating brim");
this->_make_brim();
}
this->state.set_done(psBrim);
}
if (! this->state.is_done(psWipeTower)) {
this->state.set_started(psWipeTower);
this->_clear_wipe_tower();
if (this->has_wipe_tower()) {
//this->set_status(95, "Generating wipe tower");
this->_make_wipe_tower();
}
this->state.set_done(psWipeTower);
}
BOOST_LOG_TRIVIAL(info) << "Slicing process finished.";
}
// G-code export process, running at a background thread.
// The export_gcode may die for various reasons (fails to process output_filename_format,
// write error into the G-code, cannot execute post-processing scripts).
// It is up to the caller to show an error message.
void Print::export_gcode(const std::string &path_template, GCodePreviewData *preview_data)
{
// prerequisites
this->process();
// output everything to a G-code file
// The following call may die if the output_filename_format template substitution fails.
std::string path = this->output_filepath(path_template);
std::string message = "Exporting G-code";
if (! path.empty()) {
message += " to ";
message += path;
}
this->set_status(90, message);
// The following line may die for multiple reasons.
GCode gcode;
gcode.do_export(this, path.c_str(), preview_data);
}
void Print::_make_skirt()
{
// First off we need to decide how tall the skirt must be.
@ -978,10 +1045,6 @@ void Print::_clear_wipe_tower()
void Print::_make_wipe_tower()
{
this->_clear_wipe_tower();
if (! this->has_wipe_tower())
return;
// Let the ToolOrdering class know there will be initial priming extrusions at the start of the print.
m_tool_ordering = ToolOrdering(*this, (unsigned int)-1, true);
if (! m_tool_ordering.has_wipe_tower())
@ -1153,9 +1216,4 @@ std::string Print::output_filepath(const std::string &path)
return path;
}
void Print::set_status(int percent, const std::string &message)
{
printf("Print::status %d => %s\n", percent, message.c_str());
}
}

View file

@ -5,6 +5,7 @@
#include <set>
#include <vector>
#include <string>
#include <functional>
#include "BoundingBox.hpp"
#include "Flow.hpp"
#include "PrintConfig.hpp"
@ -23,6 +24,7 @@ namespace Slic3r {
class Print;
class PrintObject;
class ModelObject;
class GCodePreviewData;
// Print step IDs for keeping track of the print state.
enum PrintStep {
@ -190,23 +192,26 @@ public:
// (layer height, first layer height, raft settings, print nozzle diameter etc).
SlicingParameters slicing_parameters() const;
private:
void slice();
void make_perimeters();
void prepare_infill();
void infill();
void generate_support_material();
void _slice();
std::string _fix_slicing_errors();
void _simplify_slices(double distance);
void _prepare_infill();
bool has_support_material() const;
void detect_surfaces_type();
void process_external_surfaces();
void discover_vertical_shells();
void bridge_over_infill();
void _make_perimeters();
void _infill();
void clip_fill_surfaces();
void discover_horizontal_shells();
void combine_infill();
void _generate_support_material();
private:
Print* _print;
ModelObject* _model_object;
Points _copies; // Slic3r::Point objects in scaled G-code coordinates
@ -232,7 +237,6 @@ public:
PrintObjectPtrs objects;
PrintRegionPtrs regions;
PlaceholderParser placeholder_parser;
// TODO: status_cb
std::string estimated_print_time;
double total_used_filament, total_extruded_volume, total_cost, total_weight;
std::map<size_t, float> filament_stats;
@ -283,13 +287,11 @@ public:
bool has_support_material() const;
void auto_assign_extruders(ModelObject* model_object) const;
void _make_skirt();
void _make_brim();
void process();
void export_gcode(const std::string &path_template, GCodePreviewData *preview_data);
// Wipe tower support.
bool has_wipe_tower() const;
void _clear_wipe_tower();
void _make_wipe_tower();
// Tool ordering of a non-sequential print has to be known to calculate the wipe tower.
// Cache it here, so it does not need to be recalculated during the G-code generation.
ToolOrdering m_tool_ordering;
@ -301,8 +303,14 @@ public:
std::string output_filename();
std::string output_filepath(const std::string &path);
typedef std::function<void(int, const std::string&)> status_callback_type;
void set_status_callback(status_callback_type cb) { m_status_callback = cb; }
void reset_status_callback() { m_status_callback = nullptr; }
// Calls a registered callback to update the status.
void set_status(int percent, const std::string &message);
void set_status(int percent, const std::string &message) {
if (m_status_callback) m_status_callback(percent, message);
else printf("%d => %s\n", percent, message.c_str());
}
// Cancel the running computation. Stop execution of all the background threads.
void cancel() { m_canceled = true; }
// Cancel the running computation. Stop execution of all the background threads.
@ -314,8 +322,14 @@ private:
bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
PrintRegionConfig _region_config_from_model_volume(const ModelVolume &volume);
void _make_skirt();
void _make_brim();
void _clear_wipe_tower();
void _make_wipe_tower();
// Has the calculation been canceled?
tbb::atomic<bool> m_canceled;
tbb::atomic<bool> m_canceled;
status_callback_type m_status_callback;
};
#define FOREACH_BASE(type, container, iterator) for (type::const_iterator iterator = (container).begin(); iterator != (container).end(); ++iterator)

View file

@ -111,6 +111,7 @@ PrintConfigDef::PrintConfigDef()
"with cooling (use a fan) before tweaking this.");
def->cli = "bridge-flow-ratio=f";
def->min = 0;
def->max = 2;
def->default_value = new ConfigOptionFloat(1);
def = this->add("bridge_speed", coFloat);

View file

@ -31,6 +31,8 @@
#include <cassert>
#endif
#define PARALLEL_FOR_CANCEL do { if (this->print()->canceled()) return; } while (0)
namespace Slic3r {
PrintObject::PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox) :
@ -104,6 +106,305 @@ bool PrintObject::reload_model_instances()
return this->set_copies(copies);
}
// 1) Decides Z positions of the layers,
// 2) Initializes layers and their regions
// 3) Slices the object meshes
// 4) Slices the modifier meshes and reclassifies the slices of the object meshes by the slices of the modifier meshes
// 5) Applies size compensation (offsets the slices in XY plane)
// 6) Replaces bad slices by the slices reconstructed from the upper/lower layer
// Resulting expolygons of layer regions are marked as Internal.
//
// this should be idempotent
void PrintObject::slice()
{
if (this->state.is_done(posSlice))
return;
this->state.set_started(posSlice);
this->_print->set_status(10, "Processing triangulated mesh");
this->_slice();
// Fix the model.
//FIXME is this the right place to do? It is done repeateadly at the UI and now here at the backend.
std::string warning = this->_fix_slicing_errors();
if (! warning.empty())
BOOST_LOG_TRIVIAL(info) << warning;
// Simplify slices if required.
if (this->_print->config.resolution)
this->_simplify_slices(scale_(this->_print->config.resolution));
if (this->layers.empty())
throw std::runtime_error("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n");
this->state.set_done(posSlice);
}
// 1) Merges typed region slices into stInternal type.
// 2) Increases an "extra perimeters" counter at region slices where needed.
// 3) Generates perimeters, gap fills and fill regions (fill regions of type stInternal).
void PrintObject::make_perimeters()
{
// prerequisites
this->slice();
if (this->state.is_done(posPerimeters))
return;
this->state.set_started(posPerimeters);
this->_print->set_status(20, "Generating perimeters");
BOOST_LOG_TRIVIAL(info) << "Generating perimeters...";
// merge slices if they were split into types
if (this->typed_slices) {
FOREACH_LAYER(this, layer_it)
(*layer_it)->merge_slices();
this->typed_slices = false;
this->state.invalidate(posPrepareInfill);
}
// compare each layer to the one below, and mark those slices needing
// one additional inner perimeter, like the top of domed objects-
// this algorithm makes sure that at least one perimeter is overlapping
// but we don't generate any extra perimeter if fill density is zero, as they would be floating
// inside the object - infill_only_where_needed should be the method of choice for printing
// hollow objects
FOREACH_REGION(this->_print, region_it) {
size_t region_id = region_it - this->_print->regions.begin();
const PrintRegion &region = **region_it;
if (!region.config.extra_perimeters
|| region.config.perimeters == 0
|| region.config.fill_density == 0
|| this->layer_count() < 2)
continue;
BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - start";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, this->layers.size() - 1),
[this, &region, region_id](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
PARALLEL_FOR_CANCEL;
LayerRegion &layerm = *this->layers[layer_idx]->regions[region_id];
const LayerRegion &upper_layerm = *this->layers[layer_idx+1]->regions[region_id];
const Polygons upper_layerm_polygons = upper_layerm.slices;
// Filter upper layer polygons in intersection_ppl by their bounding boxes?
// my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ];
const double total_loop_length = total_length(upper_layerm_polygons);
const coord_t perimeter_spacing = layerm.flow(frPerimeter).scaled_spacing();
const Flow ext_perimeter_flow = layerm.flow(frExternalPerimeter);
const coord_t ext_perimeter_width = ext_perimeter_flow.scaled_width();
const coord_t ext_perimeter_spacing = ext_perimeter_flow.scaled_spacing();
for (Surface &slice : layerm.slices.surfaces) {
for (;;) {
// compute the total thickness of perimeters
const coord_t perimeters_thickness = ext_perimeter_width/2 + ext_perimeter_spacing/2
+ (region.config.perimeters-1 + slice.extra_perimeters) * perimeter_spacing;
// define a critical area where we don't want the upper slice to fall into
// (it should either lay over our perimeters or outside this area)
const coord_t critical_area_depth = coord_t(perimeter_spacing * 1.5);
const Polygons critical_area = diff(
offset(slice.expolygon, float(- perimeters_thickness)),
offset(slice.expolygon, float(- perimeters_thickness - critical_area_depth))
);
// check whether a portion of the upper slices falls inside the critical area
const Polylines intersection = intersection_pl(to_polylines(upper_layerm_polygons), critical_area);
// only add an additional loop if at least 30% of the slice loop would benefit from it
if (total_length(intersection) <= total_loop_length*0.3)
break;
/*
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"extra.svg",
no_arrows => 1,
expolygons => union_ex($critical_area),
polylines => [ map $_->split_at_first_point, map $_->p, @{$upper_layerm->slices} ],
);
}
*/
++ slice.extra_perimeters;
}
#ifdef DEBUG
if (slice.extra_perimeters > 0)
printf(" adding %d more perimeter(s) at layer %zu\n", slice.extra_perimeters, layer_idx);
#endif
}
}
});
BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - end";
}
BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - start";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, this->layers.size()),
[this](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
PARALLEL_FOR_CANCEL;
this->layers[layer_idx]->make_perimeters();
}
}
);
BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - end";
/*
simplify slices (both layer and region slices),
we only need the max resolution for perimeters
### This makes this method not-idempotent, so we keep it disabled for now.
###$self->_simplify_slices(&Slic3r::SCALED_RESOLUTION);
*/
this->state.set_done(posPerimeters);
}
void PrintObject::prepare_infill()
{
if (this->state.is_done(posPrepareInfill))
return;
this->state.set_started(posPrepareInfill);
this->_print->set_status(30, "Preparing infill");
// This will assign a type (top/bottom/internal) to $layerm->slices.
// Then the classifcation of $layerm->slices is transfered onto
// the $layerm->fill_surfaces by clipping $layerm->fill_surfaces
// by the cummulative area of the previous $layerm->fill_surfaces.
this->detect_surfaces_type();
// Decide what surfaces are to be filled.
// Here the S_TYPE_TOP / S_TYPE_BOTTOMBRIDGE / S_TYPE_BOTTOM infill is turned to just S_TYPE_INTERNAL if zero top / bottom infill layers are configured.
// Also tiny S_TYPE_INTERNAL surfaces are turned to S_TYPE_INTERNAL_SOLID.
BOOST_LOG_TRIVIAL(info) << "Preparing fill surfaces...";
for (auto *layer : this->layers)
for (auto *region : layer->regions)
region->prepare_fill_surfaces();
// this will detect bridges and reverse bridges
// and rearrange top/bottom/internal surfaces
// It produces enlarged overlapping bridging areas.
//
// 1) S_TYPE_BOTTOMBRIDGE / S_TYPE_BOTTOM infill is grown by 3mm and clipped by the total infill area. Bridges are detected. The areas may overlap.
// 2) S_TYPE_TOP is grown by 3mm and clipped by the grown bottom areas. The areas may overlap.
// 3) Clip the internal surfaces by the grown top/bottom surfaces.
// 4) Merge surfaces with the same style. This will mostly get rid of the overlaps.
//FIXME This does not likely merge surfaces, which are supported by a material with different colors, but same properties.
this->process_external_surfaces();
// Add solid fills to ensure the shell vertical thickness.
this->discover_vertical_shells();
// Debugging output.
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
for (const Layer *layer : this->layers) {
LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("6_discover_vertical_shells-final");
layerm->export_region_fill_surfaces_to_svg_debug("6_discover_vertical_shells-final");
} // for each layer
} // for each region
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// Detect, which fill surfaces are near external layers.
// They will be split in internal and internal-solid surfaces.
// The purpose is to add a configurable number of solid layers to support the TOP surfaces
// and to add a configurable number of solid layers above the BOTTOM / BOTTOMBRIDGE surfaces
// to close these surfaces reliably.
//FIXME Vojtech: Is this a good place to add supporting infills below sloping perimeters?
this->discover_horizontal_shells();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
for (const Layer *layer : this->layers) {
LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("7_discover_horizontal_shells-final");
layerm->export_region_fill_surfaces_to_svg_debug("7_discover_horizontal_shells-final");
} // for each layer
} // for each region
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// Only active if config->infill_only_where_needed. This step trims the sparse infill,
// so it acts as an internal support. It maintains all other infill types intact.
// Here the internal surfaces and perimeters have to be supported by the sparse infill.
//FIXME The surfaces are supported by a sparse infill, but the sparse infill is only as large as the area to support.
// Likely the sparse infill will not be anchored correctly, so it will not work as intended.
// Also one wishes the perimeters to be supported by a full infill.
this->clip_fill_surfaces();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
for (const Layer *layer : this->layers) {
LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("8_clip_surfaces-final");
layerm->export_region_fill_surfaces_to_svg_debug("8_clip_surfaces-final");
} // for each layer
} // for each region
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// the following step needs to be done before combination because it may need
// to remove only half of the combined infill
this->bridge_over_infill();
// combine fill surfaces to honor the "infill every N layers" option
this->combine_infill();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
for (const Layer *layer : this->layers) {
LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("9_prepare_infill-final");
layerm->export_region_fill_surfaces_to_svg_debug("9_prepare_infill-final");
} // for each layer
} // for each region
for (const Layer *layer : this->layers) {
layer->export_region_slices_to_svg_debug("9_prepare_infill-final");
layer->export_region_fill_surfaces_to_svg_debug("9_prepare_infill-final");
} // for each layer
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
this->state.set_done(posPrepareInfill);
}
void PrintObject::infill()
{
// prerequisites
this->prepare_infill();
if (! this->state.is_done(posInfill)) {
this->state.set_started(posInfill);
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, this->layers.size()),
[this](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
PARALLEL_FOR_CANCEL;
this->layers[layer_idx]->make_fills();
}
}
);
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - end";
/* we could free memory now, but this would make this step not idempotent
### $_->fill_surfaces->clear for map @{$_->regions}, @{$object->layers};
*/
this->state.set_done(posInfill);
}
}
void PrintObject::generate_support_material()
{
if (! this->state.is_done(posSupportMaterial)) {
this->state.set_started(posSupportMaterial);
this->clear_support_layers();
if ((this->config.support_material || this->config.raft_layers > 0) && this->layers.size() > 1) {
this->_print->set_status(85, "Generating support material");
this->_generate_support_material();
}
this->state.set_done(posSupportMaterial);
char stats[128];
//FIXME this does not belong here! Why should the status bar be updated with the object weight
// at the end of object's support.?
sprintf(stats, "Weight: %.1lfg, Cost: %.1lf", this->_print->total_weight, this->_print->total_cost);
this->_print->set_status(85, stats);
}
}
void PrintObject::clear_layers()
{
for (Layer *l : this->layers)
@ -282,105 +583,6 @@ bool PrintObject::has_support_material() const
|| this->config.support_material_enforce_layers > 0;
}
void PrintObject::_prepare_infill()
{
// This will assign a type (top/bottom/internal) to $layerm->slices.
// Then the classifcation of $layerm->slices is transfered onto
// the $layerm->fill_surfaces by clipping $layerm->fill_surfaces
// by the cummulative area of the previous $layerm->fill_surfaces.
this->detect_surfaces_type();
// Decide what surfaces are to be filled.
// Here the S_TYPE_TOP / S_TYPE_BOTTOMBRIDGE / S_TYPE_BOTTOM infill is turned to just S_TYPE_INTERNAL if zero top / bottom infill layers are configured.
// Also tiny S_TYPE_INTERNAL surfaces are turned to S_TYPE_INTERNAL_SOLID.
BOOST_LOG_TRIVIAL(info) << "Preparing fill surfaces...";
for (auto *layer : this->layers)
for (auto *region : layer->regions)
region->prepare_fill_surfaces();
// this will detect bridges and reverse bridges
// and rearrange top/bottom/internal surfaces
// It produces enlarged overlapping bridging areas.
//
// 1) S_TYPE_BOTTOMBRIDGE / S_TYPE_BOTTOM infill is grown by 3mm and clipped by the total infill area. Bridges are detected. The areas may overlap.
// 2) S_TYPE_TOP is grown by 3mm and clipped by the grown bottom areas. The areas may overlap.
// 3) Clip the internal surfaces by the grown top/bottom surfaces.
// 4) Merge surfaces with the same style. This will mostly get rid of the overlaps.
//FIXME This does not likely merge surfaces, which are supported by a material with different colors, but same properties.
this->process_external_surfaces();
// Add solid fills to ensure the shell vertical thickness.
this->discover_vertical_shells();
// Debugging output.
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
for (const Layer *layer : this->layers) {
LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("6_discover_vertical_shells-final");
layerm->export_region_fill_surfaces_to_svg_debug("6_discover_vertical_shells-final");
} // for each layer
} // for each region
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// Detect, which fill surfaces are near external layers.
// They will be split in internal and internal-solid surfaces.
// The purpose is to add a configurable number of solid layers to support the TOP surfaces
// and to add a configurable number of solid layers above the BOTTOM / BOTTOMBRIDGE surfaces
// to close these surfaces reliably.
//FIXME Vojtech: Is this a good place to add supporting infills below sloping perimeters?
this->discover_horizontal_shells();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
for (const Layer *layer : this->layers) {
LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("7_discover_horizontal_shells-final");
layerm->export_region_fill_surfaces_to_svg_debug("7_discover_horizontal_shells-final");
} // for each layer
} // for each region
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// Only active if config->infill_only_where_needed. This step trims the sparse infill,
// so it acts as an internal support. It maintains all other infill types intact.
// Here the internal surfaces and perimeters have to be supported by the sparse infill.
//FIXME The surfaces are supported by a sparse infill, but the sparse infill is only as large as the area to support.
// Likely the sparse infill will not be anchored correctly, so it will not work as intended.
// Also one wishes the perimeters to be supported by a full infill.
this->clip_fill_surfaces();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
for (const Layer *layer : this->layers) {
LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("8_clip_surfaces-final");
layerm->export_region_fill_surfaces_to_svg_debug("8_clip_surfaces-final");
} // for each layer
} // for each region
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// the following step needs to be done before combination because it may need
// to remove only half of the combined infill
this->bridge_over_infill();
// combine fill surfaces to honor the "infill every N layers" option
this->combine_infill();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
for (const Layer *layer : this->layers) {
LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("9_prepare_infill-final");
layerm->export_region_fill_surfaces_to_svg_debug("9_prepare_infill-final");
} // for each layer
} // for each region
for (const Layer *layer : this->layers) {
layer->export_region_slices_to_svg_debug("9_prepare_infill-final");
layer->export_region_fill_surfaces_to_svg_debug("9_prepare_infill-final");
} // for each layer
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
}
// This function analyzes slices of a region (SurfaceCollection slices).
// Each region slice (instance of Surface) is analyzed, whether it is supported or whether it is the top surface.
// Initially all slices are of type stInternal.
@ -427,6 +629,7 @@ void PrintObject::detect_surfaces_type()
(this->config.support_material.value && this->config.support_material_contact_distance.value == 0) ?
stBottom : stBottomBridge;
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
PARALLEL_FOR_CANCEL;
// BOOST_LOG_TRIVIAL(trace) << "Detecting solid surfaces for region " << idx_region << " and layer " << layer->print_z;
Layer *layer = this->layers[idx_layer];
LayerRegion *layerm = layer->get_region(idx_region);
@ -564,6 +767,7 @@ void PrintObject::detect_surfaces_type()
tbb::blocked_range<size_t>(0, this->layers.size()),
[this, idx_region, interface_shells, &surfaces_new](const tbb::blocked_range<size_t>& range) {
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
PARALLEL_FOR_CANCEL;
LayerRegion *layerm = this->layers[idx_layer]->get_region(idx_region);
layerm->slices_to_fill_surfaces_clipped();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
@ -590,6 +794,7 @@ void PrintObject::process_external_surfaces()
tbb::blocked_range<size_t>(0, this->layers.size()),
[this, region_id](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
PARALLEL_FOR_CANCEL;
// BOOST_LOG_TRIVIAL(trace) << "Processing external surface, layer" << this->layers[layer_idx]->print_z;
this->layers[layer_idx]->get_region(region_id)->process_external_surfaces((layer_idx == 0) ? NULL : this->layers[layer_idx - 1]);
}
@ -638,6 +843,7 @@ void PrintObject::discover_vertical_shells()
const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge };
const size_t num_regions = this->_print->regions.size();
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
PARALLEL_FOR_CANCEL;
const Layer &layer = *this->layers[idx_layer];
DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[idx_layer];
// Simulate single set of perimeters over all merged regions.
@ -720,6 +926,7 @@ void PrintObject::discover_vertical_shells()
[this, idx_region, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge };
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
PARALLEL_FOR_CANCEL;
Layer &layer = *this->layers[idx_layer];
LayerRegion &layerm = *layer.regions[idx_region];
float min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f;
@ -748,7 +955,7 @@ void PrintObject::discover_vertical_shells()
// printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end());
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
PROFILE_BLOCK(discover_vertical_shells_region_layer);
PARALLEL_FOR_CANCEL;
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
static size_t debug_idx = 0;
++ debug_idx;
@ -1265,6 +1472,7 @@ end:
tbb::blocked_range<size_t>(0, this->layers.size()),
[this](const tbb::blocked_range<size_t>& range) {
for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) {
PARALLEL_FOR_CANCEL;
Layer *layer = this->layers[layer_id];
// Apply size compensation and perform clipping of multi-part objects.
float delta = float(scale_(this->config.xy_size_compensation.value));
@ -1348,6 +1556,7 @@ std::string PrintObject::_fix_slicing_errors()
tbb::blocked_range<size_t>(0, buggy_layers.size()),
[this, &buggy_layers](const tbb::blocked_range<size_t>& range) {
for (size_t buggy_layer_idx = range.begin(); buggy_layer_idx < range.end(); ++ buggy_layer_idx) {
PARALLEL_FOR_CANCEL;
size_t idx_layer = buggy_layers[buggy_layer_idx];
Layer *layer = this->layers[idx_layer];
assert(layer->slicing_errors);
@ -1424,6 +1633,7 @@ void PrintObject::_simplify_slices(double distance)
tbb::blocked_range<size_t>(0, this->layers.size()),
[this, distance](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
PARALLEL_FOR_CANCEL;
Layer *layer = this->layers[layer_idx];
for (size_t region_idx = 0; region_idx < layer->regions.size(); ++ region_idx)
layer->regions[region_idx]->slices.simplify(distance);
@ -1433,137 +1643,6 @@ void PrintObject::_simplify_slices(double distance)
BOOST_LOG_TRIVIAL(debug) << "Slicing objects - siplifying slices in parallel - end";
}
void PrintObject::_make_perimeters()
{
if (this->state.is_done(posPerimeters)) return;
this->state.set_started(posPerimeters);
BOOST_LOG_TRIVIAL(info) << "Generating perimeters...";
// merge slices if they were split into types
if (this->typed_slices) {
FOREACH_LAYER(this, layer_it)
(*layer_it)->merge_slices();
this->typed_slices = false;
this->state.invalidate(posPrepareInfill);
}
// compare each layer to the one below, and mark those slices needing
// one additional inner perimeter, like the top of domed objects-
// this algorithm makes sure that at least one perimeter is overlapping
// but we don't generate any extra perimeter if fill density is zero, as they would be floating
// inside the object - infill_only_where_needed should be the method of choice for printing
// hollow objects
FOREACH_REGION(this->_print, region_it) {
size_t region_id = region_it - this->_print->regions.begin();
const PrintRegion &region = **region_it;
if (!region.config.extra_perimeters
|| region.config.perimeters == 0
|| region.config.fill_density == 0
|| this->layer_count() < 2)
continue;
BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - start";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, this->layers.size() - 1),
[this, &region, region_id](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
LayerRegion &layerm = *this->layers[layer_idx]->regions[region_id];
const LayerRegion &upper_layerm = *this->layers[layer_idx+1]->regions[region_id];
const Polygons upper_layerm_polygons = upper_layerm.slices;
// Filter upper layer polygons in intersection_ppl by their bounding boxes?
// my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ];
const double total_loop_length = total_length(upper_layerm_polygons);
const coord_t perimeter_spacing = layerm.flow(frPerimeter).scaled_spacing();
const Flow ext_perimeter_flow = layerm.flow(frExternalPerimeter);
const coord_t ext_perimeter_width = ext_perimeter_flow.scaled_width();
const coord_t ext_perimeter_spacing = ext_perimeter_flow.scaled_spacing();
for (Surface &slice : layerm.slices.surfaces) {
for (;;) {
// compute the total thickness of perimeters
const coord_t perimeters_thickness = ext_perimeter_width/2 + ext_perimeter_spacing/2
+ (region.config.perimeters-1 + slice.extra_perimeters) * perimeter_spacing;
// define a critical area where we don't want the upper slice to fall into
// (it should either lay over our perimeters or outside this area)
const coord_t critical_area_depth = coord_t(perimeter_spacing * 1.5);
const Polygons critical_area = diff(
offset(slice.expolygon, float(- perimeters_thickness)),
offset(slice.expolygon, float(- perimeters_thickness - critical_area_depth))
);
// check whether a portion of the upper slices falls inside the critical area
const Polylines intersection = intersection_pl(to_polylines(upper_layerm_polygons), critical_area);
// only add an additional loop if at least 30% of the slice loop would benefit from it
if (total_length(intersection) <= total_loop_length*0.3)
break;
/*
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"extra.svg",
no_arrows => 1,
expolygons => union_ex($critical_area),
polylines => [ map $_->split_at_first_point, map $_->p, @{$upper_layerm->slices} ],
);
}
*/
++ slice.extra_perimeters;
}
#ifdef DEBUG
if (slice.extra_perimeters > 0)
printf(" adding %d more perimeter(s) at layer %zu\n", slice.extra_perimeters, layer_idx);
#endif
}
}
});
BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - end";
}
BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - start";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, this->layers.size()),
[this](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx)
this->layers[layer_idx]->make_perimeters();
}
);
BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - end";
/*
simplify slices (both layer and region slices),
we only need the max resolution for perimeters
### This makes this method not-idempotent, so we keep it disabled for now.
###$self->_simplify_slices(&Slic3r::SCALED_RESOLUTION);
*/
this->state.set_done(posPerimeters);
}
void PrintObject::_infill()
{
if (this->state.is_done(posInfill)) return;
this->state.set_started(posInfill);
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, this->layers.size()),
[this](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx)
this->layers[layer_idx]->make_fills();
}
);
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - end";
/* we could free memory now, but this would make this step not idempotent
### $_->fill_surfaces->clear for map @{$_->regions}, @{$object->layers};
*/
this->state.set_done(posInfill);
}
// Only active if config->infill_only_where_needed. This step trims the sparse infill,
// so it acts as an internal support. It maintains all other infill types intact.
// Here the internal surfaces and perimeters have to be supported by the sparse infill.

View file

@ -56,6 +56,7 @@ REGISTER_CLASS(SurfaceCollection, "Surface::Collection");
REGISTER_CLASS(PrintObjectSupportMaterial, "Print::SupportMaterial2");
REGISTER_CLASS(TriangleMesh, "TriangleMesh");
REGISTER_CLASS(AppConfig, "GUI::AppConfig");
REGISTER_CLASS(BackgroundSlicingProcess, "GUI::BackgroundSlicingProcess");
REGISTER_CLASS(GLShader, "GUI::_3DScene::GLShader");
REGISTER_CLASS(GLVolume, "GUI::_3DScene::GLVolume");
REGISTER_CLASS(GLVolumeCollection, "GUI::_3DScene::GLVolume::Collection");

View file

@ -0,0 +1,138 @@
#include "BackgroundSlicingProcess.hpp"
#include "GUI.hpp"
#include "../../libslic3r/Print.hpp"
#include <wx/event.h>
#include <wx/panel.h>
//#undef NDEBUG
#include <cassert>
#include <stdexcept>
namespace Slic3r {
namespace GUI {
extern wxPanel *g_wxPlater;
};
void BackgroundSlicingProcess::thread_proc()
{
std::unique_lock<std::mutex> lck(m_mutex);
// Let the caller know we are ready to run the background processing task.
m_state = STATE_IDLE;
lck.unlock();
m_condition.notify_one();
for (;;) {
assert(m_state == STATE_IDLE);
// Wait until a new task is ready to be executed, or this thread should be finished.
lck.lock();
m_condition.wait(lck, [this](){ return m_state == STATE_STARTED || m_state == STATE_EXIT; });
if (m_state == STATE_EXIT)
// Exiting this thread.
break;
// Process the background slicing task.
m_state = STATE_RUNNING;
lck.unlock();
std::string error;
try {
assert(m_print != nullptr);
m_print->process();
if (m_print->canceled())
return;
printf("PReparing m_event_sliced_id command\n");
wxCommandEvent evt(m_event_sliced_id);
printf("Issuing m_event_sliced_id command\n");
wxQueueEvent(GUI::g_wxPlater, evt.Clone());
GUI::g_wxPlater->ProcessWindowEvent(evt);
//GUI::g_wxPlater->ProcessEvent(evt);
printf("Done with m_event_sliced_id command\n");
m_print->export_gcode(m_output_path, m_gcode_preview_data);
} catch (std::exception &ex) {
error = ex.what();
} catch (...) {
error = "Unknown C++ exception.";
}
lck.lock();
m_state = m_print->canceled() ? STATE_CANCELED : STATE_FINISHED;
wxCommandEvent evt(m_event_finished_id);
evt.SetString(error);
evt.SetInt(m_print->canceled() ? -1 : (error.empty() ? 1 : 0));
wxQueueEvent(GUI::g_wxPlater, evt.Clone());
lck.unlock();
// Let the UI thread wake up if it is waiting for the background task to finish.
m_condition.notify_one();
// Let the UI thread see the result.
}
m_state = STATE_EXITED;
lck.unlock();
// End of the background processing thread. The UI thread should join m_thread now.
}
void BackgroundSlicingProcess::join_background_thread()
{
std::unique_lock<std::mutex> lck(m_mutex);
if (m_state == STATE_INITIAL) {
// Worker thread has not been started yet.
assert(! m_thread.joinable());
} else {
assert(m_state == STATE_IDLE);
assert(m_thread.joinable());
// Notify the worker thread to exit.
m_state = STATE_EXIT;
lck.unlock();
m_condition.notify_one();
// Wait until the worker thread exits.
m_thread.join();
}
}
bool BackgroundSlicingProcess::start()
{
std::unique_lock<std::mutex> lck(m_mutex);
if (m_state == STATE_INITIAL) {
// The worker thread is not running yet. Start it.
assert(! m_thread.joinable());
m_thread = std::thread([this]{this->thread_proc();});
// Wait until the worker thread is ready to execute the background processing task.
m_condition.wait(lck, [this](){ return m_state == STATE_IDLE; });
}
assert(m_state == STATE_IDLE || this->running());
if (this->running())
// The background processing thread is already running.
return false;
if (! this->idle())
throw std::runtime_error("Cannot start a background task, the worker thread is not idle.");
m_state = STATE_STARTED;
lck.unlock();
m_condition.notify_one();
return true;
}
bool BackgroundSlicingProcess::stop()
{
std::unique_lock<std::mutex> lck(m_mutex);
if (m_state == STATE_INITIAL)
return false;
assert(this->running());
if (m_state == STATE_STARTED || m_state == STATE_RUNNING) {
m_print->cancel();
// Wait until the background processing stops by being canceled.
lck.unlock();
m_condition.wait(lck, [this](){ return m_state == STATE_CANCELED; });
}
return true;
}
// Apply config over the print. Returns false, if the new config values caused any of the already
// processed steps to be invalidated, therefore the task will need to be restarted.
bool BackgroundSlicingProcess::apply_config(DynamicPrintConfig *config)
{
/*
// apply new config
my $invalidated = $self->{print}->apply_config(wxTheApp->{preset_bundle}->full_config);
*/
return true;
}
}; // namespace Slic3r

View file

@ -0,0 +1,82 @@
#ifndef slic3r_GUI_BackgroundSlicingProcess_hpp_
#define slic3r_GUI_BackgroundSlicingProcess_hpp_
#include <string>
#include <condition_variable>
#include <mutex>
#include <thread>
namespace Slic3r {
class DynamicPrintConfig;
class GCodePreviewData;
class Print;
// Support for the GUI background processing (Slicing and G-code generation).
// As of now this class is not declared in Slic3r::GUI due to the Perl bindings limits.
class BackgroundSlicingProcess
{
public:
BackgroundSlicingProcess() {}
~BackgroundSlicingProcess() { this->stop(); this->join_background_thread(); }
void set_print(Print *print) { m_print = print; }
void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; }
void set_sliced_event(int event_id) { m_event_sliced_id = event_id; }
void set_finished_event(int event_id) { m_event_finished_id = event_id; }
// Start the background processing. Returns false if the background processing was already running.
bool start();
// Cancel the background processing. Returns false if the background processing was not running.
// A stopped background processing may be restarted with start().
bool stop();
// Apply config over the print. Returns false, if the new config values caused any of the already
// processed steps to be invalidated, therefore the task will need to be restarted.
bool apply_config(DynamicPrintConfig *config);
enum State {
// m_thread is not running yet, or it did not reach the STATE_IDLE yet (it does not wait on the condition yet).
STATE_INITIAL = 0,
// m_thread is waiting for the task to execute.
STATE_IDLE,
STATE_STARTED,
// m_thread is executing a task.
STATE_RUNNING,
// m_thread finished executing a task, and it is waiting until the UI thread picks up the results.
STATE_FINISHED,
// m_thread finished executing a task, the task has been canceled by the UI thread, therefore the UI thread will not be notified.
STATE_CANCELED,
// m_thread exited the loop and it is going to finish. The UI thread should join on m_thread.
STATE_EXIT,
STATE_EXITED,
};
State state() const { return m_state; }
bool idle() const { return m_state == STATE_IDLE; }
bool running() const { return m_state == STATE_STARTED || m_state == STATE_RUNNING || m_state == STATE_FINISHED || m_state == STATE_CANCELED; }
private:
void thread_proc();
void start_background_thread();
void join_background_thread();
Print *m_print = nullptr;
GCodePreviewData *m_gcode_preview_data = nullptr;
std::string m_output_path;
// Thread, on which the background processing is executed. The thread will always be present
// and ready to execute the slicing process.
std::thread m_thread;
// Mutex and condition variable to synchronize m_thread with the UI thread.
std::mutex m_mutex;
std::condition_variable m_condition;
State m_state = STATE_INITIAL;
// wxWidgets command ID to be sent to the platter to inform that the slicing is finished, and the G-code export will continue.
int m_event_sliced_id = 0;
// wxWidgets command ID to be sent to the platter to inform that the task finished.
int m_event_finished_id = 0;
};
}; // namespace Slic3r
#endif /* slic3r_GUI_BackgroundSlicingProcess_hpp_ */

View file

@ -47,6 +47,8 @@
#include "Preferences.hpp"
#include "PresetBundle.hpp"
#include "../../libslic3r/Print.hpp"
namespace Slic3r { namespace GUI {
#if __APPLE__
@ -172,6 +174,7 @@ void break_to_debugger()
wxApp *g_wxApp = nullptr;
wxFrame *g_wxMainFrame = nullptr;
wxNotebook *g_wxTabPanel = nullptr;
wxPanel *g_wxPlater = nullptr;
AppConfig *g_AppConfig = nullptr;
PresetBundle *g_PresetBundle= nullptr;
@ -197,6 +200,11 @@ void set_tab_panel(wxNotebook *tab_panel)
g_wxTabPanel = tab_panel;
}
void set_plater(wxPanel *plater)
{
g_wxPlater = plater;
}
void set_app_config(AppConfig *app_config)
{
g_AppConfig = app_config;
@ -507,6 +515,18 @@ void warning_catcher(wxWindow* parent, wxString message){
msg->ShowModal();
}
// Assign a Lambda to the print object to emit a wxWidgets Command with the provided ID
// to deliver a progress status message.
void set_print_callback_event(Print *print, int id)
{
print->set_status_callback([id](int percent, const std::string &message){
wxCommandEvent event(id);
event.SetInt(percent);
event.SetString(message);
wxQueueEvent(g_wxMainFrame, event.Clone());
});
}
wxApp* get_app(){
return g_wxApp;
}

View file

@ -11,6 +11,7 @@ class wxFrame;
class wxWindow;
class wxMenuBar;
class wxNotebook;
class wxPanel;
class wxComboCtrl;
class wxString;
class wxArrayString;
@ -23,6 +24,7 @@ namespace Slic3r {
class PresetBundle;
class PresetCollection;
class Print;
class AppConfig;
class DynamicPrintConfig;
class TabIface;
@ -73,6 +75,7 @@ void break_to_debugger();
void set_wxapp(wxApp *app);
void set_main_frame(wxFrame *main_frame);
void set_tab_panel(wxNotebook *tab_panel);
void set_plater(wxPanel *plater);
void set_app_config(AppConfig *app_config);
void set_preset_bundle(PresetBundle *preset_bundle);
@ -98,6 +101,10 @@ void show_error(wxWindow* parent, wxString message);
void show_info(wxWindow* parent, wxString message, wxString title);
void warning_catcher(wxWindow* parent, wxString message);
// Assign a Lambda to the print object to emit a wxWidgets Command with the provided ID
// to deliver a progress status message.
void set_print_callback_event(Print *print, int id);
// load language saved at application config
bool load_language();
// save language at application config