diff options
author | bubnikv <bubnikv@gmail.com> | 2018-03-23 13:41:20 +0300 |
---|---|---|
committer | bubnikv <bubnikv@gmail.com> | 2018-03-23 13:41:20 +0300 |
commit | e931f7501001feb8e2af29489e218ecca7080f48 (patch) | |
tree | 0f9e937e41d9ae71fcd037d283931f14b180aaa1 /xs | |
parent | 86b79f89ad423e2dacaadb7465df51d9f9878862 (diff) |
Work in progress: Good bye, Perl Threads!
Diffstat (limited to 'xs')
-rw-r--r-- | xs/CMakeLists.txt | 3 | ||||
-rw-r--r-- | xs/lib/Slic3r/XS.pm | 7 | ||||
-rw-r--r-- | xs/src/libslic3r/Print.cpp | 76 | ||||
-rw-r--r-- | xs/src/libslic3r/Print.hpp | 36 | ||||
-rw-r--r-- | xs/src/libslic3r/PrintConfig.cpp | 1 | ||||
-rw-r--r-- | xs/src/libslic3r/PrintObject.cpp | 541 | ||||
-rw-r--r-- | xs/src/perlglue.cpp | 1 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/BackgroundSlicingProcess.cpp | 138 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/BackgroundSlicingProcess.hpp | 82 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/GUI.cpp | 20 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/GUI.hpp | 7 | ||||
-rw-r--r-- | xs/t/20_print.t | 2 | ||||
-rw-r--r-- | xs/xsp/GUI.xsp | 6 | ||||
-rw-r--r-- | xs/xsp/GUI_BackgroundSlicingProcess.xsp | 23 | ||||
-rw-r--r-- | xs/xsp/Print.xsp | 100 | ||||
-rw-r--r-- | xs/xsp/my.map | 2 | ||||
-rw-r--r-- | xs/xsp/typemap.xspt | 2 |
17 files changed, 712 insertions, 335 deletions
diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 63b99a835..0a76721a4 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -171,6 +171,8 @@ add_library(libslic3r STATIC add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/AppConfig.cpp ${LIBDIR}/slic3r/GUI/AppConfig.hpp + ${LIBDIR}/slic3r/GUI/BackgroundSlicingProcess.cpp + ${LIBDIR}/slic3r/GUI/BackgroundSlicingProcess.hpp ${LIBDIR}/slic3r/GUI/3DScene.cpp ${LIBDIR}/slic3r/GUI/3DScene.hpp ${LIBDIR}/slic3r/GUI/GLShader.cpp @@ -331,6 +333,7 @@ set(XS_XSP_FILES ${XSP_DIR}/Geometry.xsp ${XSP_DIR}/GUI.xsp ${XSP_DIR}/GUI_AppConfig.xsp + ${XSP_DIR}/GUI_BackgroundSlicingProcess.xsp ${XSP_DIR}/GUI_3DScene.xsp ${XSP_DIR}/GUI_Preset.xsp ${XSP_DIR}/GUI_Tab.xsp diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 47a584343..5105ee0ad 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -239,23 +239,16 @@ sub new { ); } -package Slic3r::GUI::_3DScene::GLShader; -sub CLONE_SKIP { 1 } - package Slic3r::GUI::_3DScene::GLVolume::Collection; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; -sub CLONE_SKIP { 1 } - package Slic3r::GUI::PresetCollection; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; -sub CLONE_SKIP { 1 } - package main; for my $class (qw( Slic3r::BridgeDetector diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index a3f5e231f..a74afdaec 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -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()); -} - } diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index c56e64c6c..dfde2650a 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -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) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index d7ee1e591..298fe9207 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -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); diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index c61fc102b..dd2c4e377 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -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 ®ion = **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, ®ion, 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 ®ion = **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, ®ion, 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. diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index d7c9a590a..58d553966 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -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"); diff --git a/xs/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/xs/src/slic3r/GUI/BackgroundSlicingProcess.cpp new file mode 100644 index 000000000..900fdfa43 --- /dev/null +++ b/xs/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -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 diff --git a/xs/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/xs/src/slic3r/GUI/BackgroundSlicingProcess.hpp new file mode 100644 index 000000000..988138d2e --- /dev/null +++ b/xs/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -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_ */ diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 0410b7969..270a5d198 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -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; } diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 084b6de46..506454e56 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -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 diff --git a/xs/t/20_print.t b/xs/t/20_print.t index e535cdd8c..0ef194ecf 100644 --- a/xs/t/20_print.t +++ b/xs/t/20_print.t @@ -10,8 +10,6 @@ use Test::More tests => 5; my $print = Slic3r::Print->new; isa_ok $print, 'Slic3r::Print'; isa_ok $print->config, 'Slic3r::Config::Static::Ref'; - isa_ok $print->default_object_config, 'Slic3r::Config::Static::Ref'; - isa_ok $print->default_region_config, 'Slic3r::Config::Static::Ref'; isa_ok $print->placeholder_parser, 'Slic3r::GCode::PlaceholderParser::Ref'; } diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index d306f12ce..a7f3bd554 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -32,6 +32,9 @@ void set_main_frame(SV *ui) void set_tab_panel(SV *ui) %code%{ Slic3r::GUI::set_tab_panel((wxNotebook*)wxPli_sv_2_object(aTHX_ ui, "Wx::Notebook")); %}; + +void set_plater(SV *ui) + %code%{ Slic3r::GUI::set_plater((wxPanel*)wxPli_sv_2_object(aTHX_ ui, "Wx::Panel")); %}; void add_debug_menu(SV *ui, int event_language_change) %code%{ Slic3r::GUI::add_debug_menu((wxMenuBar*)wxPli_sv_2_object(aTHX_ ui, "Wx::MenuBar"), event_language_change); %}; @@ -65,5 +68,8 @@ void add_frequently_changed_parameters(SV *ui_parent, SV *ui_sizer, SV *ui_p_siz (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"), (wxFlexGridSizer*)wxPli_sv_2_object(aTHX_ ui_p_sizer, "Wx::FlexGridSizer")); %}; +void set_print_callback_event(Print *print, int id) + %code%{ Slic3r::GUI::set_print_callback_event(print, id); %}; + std::string fold_utf8_to_ascii(const char *src) %code%{ RETVAL = Slic3r::fold_utf8_to_ascii(src); %}; diff --git a/xs/xsp/GUI_BackgroundSlicingProcess.xsp b/xs/xsp/GUI_BackgroundSlicingProcess.xsp new file mode 100644 index 000000000..d12c7f8e3 --- /dev/null +++ b/xs/xsp/GUI_BackgroundSlicingProcess.xsp @@ -0,0 +1,23 @@ + +%module{Slic3r::XS}; + +%{ +#include <xsinit.h> +#include "slic3r/GUI/BackgroundSlicingProcess.hpp" +%} + +%name{Slic3r::GUI::BackgroundSlicingProcess} class BackgroundSlicingProcess { + BackgroundSlicingProcess(); + ~BackgroundSlicingProcess(); + + void set_print(Print *print); + void set_gcode_preview_data(GCodePreviewData *gpd); + void set_sliced_event(int event_id); + void set_finished_event(int event_id); + + bool start(); + bool stop(); + bool apply_config(DynamicPrintConfig *config); + + bool running(); +}; diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index cbc04a804..4ad4f74b6 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -27,7 +27,6 @@ _constant() %} - %name{Slic3r::Print::Region} class PrintRegion { // owned by Print, no constructor/destructor @@ -39,16 +38,9 @@ _constant() %code%{ RETVAL = THIS->flow(role, layer_height, bridge, first_layer, width, *object); %}; }; - %name{Slic3r::Print::Object} class PrintObject { // owned by Print, no constructor/destructor - void add_region_volume(int region_id, int volume_id); - std::vector<int> get_region_volumes(int region_id) - %code%{ - if (0 <= region_id && region_id < THIS->region_volumes.size()) - RETVAL = THIS->region_volumes[region_id]; - %}; int region_count() %code%{ RETVAL = THIS->print()->regions.size(); %}; @@ -67,57 +59,22 @@ _constant() Points _shifted_copies() %code%{ RETVAL = THIS->_shifted_copies; %}; - void set_shifted_copies(Points value) - %code%{ THIS->_shifted_copies = value; %}; bool add_copy(Pointf* point) %code%{ RETVAL = THIS->add_copy(*point); %}; bool delete_last_copy(); - bool delete_all_copies(); - bool set_copies(Points copies); bool reload_model_instances(); void set_layer_height_ranges(t_layer_height_ranges layer_height_ranges) %code%{ THIS->layer_height_ranges = layer_height_ranges; %}; - void set_layer_height_profile(std::vector<double> profile) - %code%{ THIS->layer_height_profile = profile; %}; - size_t total_layer_count(); size_t layer_count(); - void clear_layers(); Ref<Layer> get_layer(int idx); - Ref<Layer> add_layer(int id, coordf_t height, coordf_t print_z, - coordf_t slice_z); size_t support_layer_count(); - void clear_support_layers(); Ref<SupportLayer> get_support_layer(int idx); bool step_done(PrintObjectStep step) %code%{ RETVAL = THIS->state.is_done(step); %}; - void set_step_done(PrintObjectStep step) - %code%{ THIS->state.set_done(step); %}; - void set_step_started(PrintObjectStep step) - %code%{ THIS->state.set_started(step); %}; - - void _slice(); - std::string _fix_slicing_errors(); - void _simplify_slices(double distance); - void _prepare_infill(); - void detect_surfaces_type(); - void process_external_surfaces(); - void _make_perimeters(); - void _infill(); - void _generate_support_material(); - - std::vector<double> get_layer_height_min_max() - %code%{ - SlicingParameters slicing_params = THIS->slicing_parameters(); - RETVAL.push_back(slicing_params.min_layer_height); - RETVAL.push_back(slicing_params.max_layer_height); - RETVAL.push_back(slicing_params.first_print_layer_height); - RETVAL.push_back(slicing_params.first_object_layer_height); - RETVAL.push_back(slicing_params.layer_height); - %}; void adjust_layer_height_profile(coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, int action) %code%{ @@ -129,25 +86,16 @@ _constant() %}; void reset_layer_height_profile(); - - int ptr() - %code%{ RETVAL = (int)(intptr_t)THIS; %}; }; - %name{Slic3r::Print} class Print { Print(); ~Print(); Ref<StaticPrintConfig> config() %code%{ RETVAL = &THIS->config; %}; - Ref<StaticPrintConfig> default_object_config() - %code%{ RETVAL = &THIS->default_object_config; %}; - Ref<StaticPrintConfig> default_region_config() - %code%{ RETVAL = &THIS->default_region_config; %}; Ref<PlaceholderParser> placeholder_parser() %code%{ RETVAL = &THIS->placeholder_parser; %}; - // TODO: status_cb Ref<ExtrusionEntityCollection> skirt() %code%{ RETVAL = &THIS->skirt; %}; Ref<ExtrusionEntityCollection> brim() @@ -176,20 +124,7 @@ _constant() %code%{ RETVAL = THIS->state.is_done(step); %}; bool object_step_done(PrintObjectStep step) %code%{ RETVAL = THIS->step_done(step); %}; - void set_step_done(PrintStep step) - %code%{ THIS->state.set_done(step); %}; - void set_step_started(PrintStep step) - %code%{ THIS->state.set_started(step); %}; - void clear_filament_stats() - %code%{ - THIS->filament_stats.clear(); - %}; - void set_filament_stats(int extruder_id, float length) - %code%{ - THIS->filament_stats.insert(std::pair<size_t,float>(extruder_id, 0)); - THIS->filament_stats[extruder_id] += length; - %}; SV* filament_stats() %code%{ HV* hv = newHV(); @@ -203,7 +138,6 @@ _constant() RETVAL = newRV_noinc((SV*)hv); } %}; - void _simplify_slices(double distance); double max_allowed_layer_height() const; bool has_support_material() const; void auto_assign_extruders(ModelObject* model_object); @@ -220,7 +154,6 @@ _constant() bool apply_config(DynamicPrintConfig* config) %code%{ RETVAL = THIS->apply_config(*config); %}; bool has_infinite_skirt(); - bool has_skirt(); std::vector<unsigned int> extruders() const; int validate() %code%{ std::string err = THIS->validate(); @@ -230,16 +163,33 @@ _constant() %}; Clone<BoundingBox> bounding_box(); Clone<BoundingBox> total_bounding_box(); - double skirt_first_layer_height(); - Clone<Flow> brim_flow(); - Clone<Flow> skirt_flow(); - void _make_skirt(); - void _make_brim(); + void set_callback_event(int evt) %code%{ + %}; - bool has_wipe_tower(); - void _clear_wipe_tower(); - void _make_wipe_tower(); + void process() %code%{ + try { + THIS->process(); + } catch (std::exception& e) { + croak(e.what()); + } + %}; + + void export_gcode_with_preview_data(char *path_template, GCodePreviewData *preview_data) %code%{ + try { + THIS->export_gcode(path_template, preview_data); + } catch (std::exception& e) { + croak(e.what()); + } + %}; + + void export_gcode(char *path_template) %code%{ + try { + THIS->export_gcode(path_template, nullptr); + } catch (std::exception& e) { + croak(e.what()); + } + %}; %{ diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 87a8d8d86..67693685a 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -217,6 +217,8 @@ Clone<PrintObjectSupportMaterial> O_OBJECT_SLIC3R_T AppConfig* O_OBJECT_SLIC3R Ref<AppConfig> O_OBJECT_SLIC3R_T +BackgroundSlicingProcess* O_OBJECT_SLIC3R +Ref<BackgroundSlicingProcess> O_OBJECT_SLIC3R_T GLShader* O_OBJECT_SLIC3R Ref<GLShader> O_OBJECT_SLIC3R_T diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 0214a158d..461a12e74 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -196,6 +196,8 @@ %typemap{Clone<ModelInstancePtrs>}{simple}; %typemap{AppConfig*}; %typemap{Ref<AppConfig>}{simple}; +%typemap{BackgroundSlicingProcess*}; +%typemap{Ref<BackgroundSlicingProcess>}{simple}; %typemap{GLShader*}; %typemap{Ref<GLShader>}{simple}; %typemap{GLVolume*}; |