diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 1cd20c6fa6..2ff6a34435 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -242,6 +242,14 @@ BoundingBoxf3 Model::bounding_box() const return bb; } +unsigned int Model::update_print_volume_state(const BoundingBoxf3 &print_volume) +{ + unsigned int num_printable = 0; + for (ModelObject *model_object : this->objects) + num_printable += model_object->check_instances_print_volume_state(print_volume); + return num_printable; +} + void Model::center_instances_around_point(const Vec2d &point) { BoundingBoxf3 bb; @@ -875,26 +883,32 @@ void ModelObject::repair() v->mesh.repair(); } -// Called by Print::validate() from the UI thread. -void ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_volume) +unsigned int ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_volume) { - for (const ModelVolume* vol : this->volumes) - { - if (vol->is_model_part()) - { - for (ModelInstance* inst : this->instances) - { - BoundingBoxf3 bb = vol->get_convex_hull().transformed_bounding_box(inst->world_matrix()); - + unsigned int num_printable = 0; + enum { + INSIDE = 1, + OUTSIDE = 2 + }; + for (ModelInstance *model_instance : this->instances) { + unsigned int inside_outside = 0; + for (const ModelVolume *vol : this->volumes) + if (vol->is_model_part()) { + BoundingBoxf3 bb = vol->get_convex_hull().transformed_bounding_box(model_instance->world_matrix()); if (print_volume.contains(bb)) - inst->print_volume_state = ModelInstance::PVS_Inside; + inside_outside |= INSIDE; else if (print_volume.intersects(bb)) - inst->print_volume_state = ModelInstance::PVS_Partly_Outside; + inside_outside |= INSIDE | OUTSIDE; else - inst->print_volume_state = ModelInstance::PVS_Fully_Outside; + inside_outside |= OUTSIDE; } - } + model_instance->print_volume_state = + (inside_outside == (INSIDE | OUTSIDE)) ? ModelInstance::PVS_Partly_Outside : + (inside_outside == INSIDE) ? ModelInstance::PVS_Inside : ModelInstance::PVS_Fully_Outside; + if (inside_outside == INSIDE) + ++ num_printable; } + return num_printable; } void ModelObject::print_info() const diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 81f101636d..9635c2fd2f 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -156,7 +156,7 @@ public: void repair(); // Called by Print::validate() from the UI thread. - void check_instances_print_volume_state(const BoundingBoxf3& print_volume); + unsigned int check_instances_print_volume_state(const BoundingBoxf3& print_volume); // Print object statistics to console. void print_info() const; @@ -411,6 +411,9 @@ public: bool add_default_instances(); // Returns approximate axis aligned bounding box of this model BoundingBoxf3 bounding_box() const; + // Set the print_volume_state of PrintObject::instances, + // return total number of printable objects. + unsigned int update_print_volume_state(const BoundingBoxf3 &print_volume); void center_instances_around_point(const Vec2d &point); void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); } TriangleMesh mesh() const; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index cfbbda44b4..d4fed0d5f9 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -852,7 +852,11 @@ bool Print::apply(const Model &model, const DynamicPrintConfig &config_in) bool modifiers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::PARAMETER_MODIFIER); bool support_blockers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::SUPPORT_BLOCKER); bool support_enforcers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::SUPPORT_ENFORCER); - if (model_parts_differ || modifiers_differ) { + if (model_parts_differ || modifiers_differ || + model_object.origin_translation != model_object_new.origin_translation || + model_object.layer_height_ranges != model_object_new.layer_height_ranges || + model_object.layer_height_profile != model_object_new.layer_height_profile || + model_object.layer_height_profile_valid != model_object_new.layer_height_profile_valid) { // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); for (auto it = range.first; it != range.second; ++ it) { @@ -889,6 +893,9 @@ bool Print::apply(const Model &model, const DynamicPrintConfig &config_in) it->print_object->config_apply_only(new_config, diff, true); } } + model_object.name = model_object_new.name; + model_object.input_file = model_object_new.input_file; + model_object.instances = model_object_new.instances; } } @@ -936,11 +943,10 @@ bool Print::apply(const Model &model, const DynamicPrintConfig &config_in) print_objects_new.emplace_back(print_object); print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); } else if ((*it_old)->print_object->copies() != new_instances.copies) { - // The PrintObject already exists and the copies differ. The only step currently sensitive to the order is the G-code generator. - // Stop it. - this->invalidate_step(psGCodeExport); + // The PrintObject already exists and the copies differ. (*it_old)->print_object->set_copies(new_instances.copies); - } + print_objects_new.emplace_back((*it_old)->print_object); + } } } if (m_objects != print_objects_new) { @@ -958,7 +964,7 @@ bool Print::apply(const Model &model, const DynamicPrintConfig &config_in) int idx_region = 0; for (const auto &volumes : print_object->region_volumes) { if (! volumes.empty()) - ++ m_regions[idx_region]; + ++ m_regions[idx_region]->m_refcnt; ++ idx_region; } } @@ -1110,23 +1116,7 @@ bool Print::has_skirt() const std::string Print::validate() const { - BoundingBox bed_box_2D = get_extents(Polygon::new_scale(m_config.bed_shape.values)); - BoundingBoxf3 print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), scale_(m_config.max_print_height))); - // Allow the objects to protrude below the print bed, only the part of the object above the print bed will be sliced. - print_volume.min(2) = -1e10; - unsigned int printable_count = 0; - { - // Lock due to the po->reload_model_instances() - tbb::mutex::scoped_lock lock(m_mutex); - for (PrintObject *po : m_objects) { - po->model_object()->check_instances_print_volume_state(print_volume); - po->reload_model_instances(); - if (po->is_printable()) - ++ printable_count; - } - } - - if (printable_count == 0) + if (m_objects.empty()) return L("All objects are outside of the print volume."); if (m_config.complete_objects) { diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index f4fd2235be..e765b8801e 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -96,6 +96,7 @@ public: #endif mtx.unlock(); cancel(); + m_state[step] = INVALID; mtx.lock(); } return invalidated; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index c62dd000ca..38b2e678b9 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -87,23 +87,25 @@ bool PrintObject::delete_last_copy() bool PrintObject::set_copies(const Points &points) { - bool copies_num_changed = m_copies.size() != points.size(); - - // order copies with a nearest neighbor search and translate them by _copies_shift - m_copies.clear(); - m_copies.reserve(points.size()); - - // order copies with a nearest-neighbor search - std::vector ordered_copies; - Slic3r::Geometry::chained_path(points, ordered_copies); - - for (size_t point_idx : ordered_copies) - m_copies.push_back(points[point_idx] + m_copies_shift); - - bool invalidated = m_print->invalidate_step(psSkirt); - invalidated |= m_print->invalidate_step(psBrim); - if (copies_num_changed) - invalidated |= m_print->invalidate_step(psWipeTower); + // Order copies with a nearest-neighbor search. + std::vector copies; + { + std::vector ordered_copies; + Slic3r::Geometry::chained_path(points, ordered_copies); + copies.reserve(ordered_copies.size()); + for (size_t point_idx : ordered_copies) + copies.emplace_back(points[point_idx] + m_copies_shift); + } + // Invalidate and set copies. + bool invalidated = false; + if (copies != m_copies) { + invalidated = m_print->invalidate_step(psSkirt); + invalidated |= m_print->invalidate_step(psBrim); + if (copies.size() != m_copies.size()) + invalidated |= m_print->invalidate_step(psWipeTower); + invalidated |= m_print->invalidate_step(psGCodeExport); + m_copies = copies; + } return invalidated; } diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 665351c393..c8a6e2130b 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -858,12 +858,12 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector::const_iterator min_layer, max_layer; min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z - max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z) - 1; // last layer whose slice_z is <= max_z + max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z); // first layer, whose slice_z is > max_z #ifdef SLIC3R_TRIANGLEMESH_DEBUG - printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin())); + printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin()) - 1); #endif /* SLIC3R_TRIANGLEMESH_DEBUG */ - for (std::vector::const_iterator it = min_layer; it != max_layer + 1; ++it) { + for (std::vector::const_iterator it = min_layer; it != max_layer; ++it) { std::vector::size_type layer_idx = it - z.begin(); IntersectionLine il; if (this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il) == TriangleMeshSlicer::Slicing) { diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 7c4a4c0399..71250598b7 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -57,12 +57,20 @@ void BackgroundSlicingProcess::thread_proc() if (! m_print->canceled()) { wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_sliced_id)); m_print->export_gcode(m_temp_output_path, m_gcode_preview_data); - if (! m_print->canceled() && ! m_output_path.empty()) { - if (copy_file(m_temp_output_path, m_output_path) != 0) - throw std::runtime_error("Copying of the temporary G-code to the output G-code failed"); - m_print->set_status(95, "Running post-processing scripts"); - run_post_process_scripts(m_output_path, m_print->config()); - } + if (! m_print->canceled() && ! this->is_step_done(bspsGCodeFinalize)) { + this->set_step_started(bspsGCodeFinalize); + if (! m_export_path.empty()) { + //FIXME localize the messages + if (copy_file(m_temp_output_path, m_export_path) != 0) + throw std::runtime_error("Copying of the temporary G-code to the output G-code failed"); + m_print->set_status(95, "Running post-processing scripts"); + run_post_process_scripts(m_export_path, m_print->config()); + m_print->set_status(100, "G-code file exported to " + m_export_path); + } else { + m_print->set_status(100, "Slicing complete"); + } + this->set_step_done(bspsGCodeFinalize); + } } } catch (CanceledException &ex) { // Canceled, this is all right. @@ -133,7 +141,7 @@ bool BackgroundSlicingProcess::stop() { std::unique_lock lck(m_mutex); if (m_state == STATE_INITIAL) { - this->m_output_path.clear(); +// this->m_export_path.clear(); return false; } // assert(this->running()); @@ -147,7 +155,7 @@ bool BackgroundSlicingProcess::stop() // In the "Finished" or "Canceled" state. Reset the state to "Idle". m_state = STATE_IDLE; } - this->m_output_path.clear(); +// this->m_export_path.clear(); return true; } @@ -169,4 +177,53 @@ bool BackgroundSlicingProcess::apply(const Model &model, const DynamicPrintConfi return invalidated; } +// Set the output path of the G-code. +void BackgroundSlicingProcess::schedule_export(const std::string &path) +{ + assert(m_export_path.empty()); + if (! m_export_path.empty()) + return; + + // Guard against entering the export step before changing the export path. + tbb::mutex::scoped_lock lock(m_step_state_mutex); + this->invalidate_step(bspsGCodeFinalize); + m_export_path = path; +} + +void BackgroundSlicingProcess::reset_export() +{ + assert(! this->running()); + if (! this->running()) { + m_export_path.clear(); + // invalidate_step expects the mutex to be locked. + tbb::mutex::scoped_lock lock(m_step_state_mutex); + this->invalidate_step(bspsGCodeFinalize); + } +} + +void BackgroundSlicingProcess::set_step_started(BackgroundSlicingProcessStep step) +{ + m_step_state.set_started(step, m_step_state_mutex); + if (m_print->canceled()) + throw CanceledException(); +} + +void BackgroundSlicingProcess::set_step_done(BackgroundSlicingProcessStep step) +{ + m_step_state.set_done(step, m_step_state_mutex); + if (m_print->canceled()) + throw CanceledException(); +} + +bool BackgroundSlicingProcess::invalidate_step(BackgroundSlicingProcessStep step) +{ + bool invalidated = m_step_state.invalidate(step, m_step_state_mutex, [this](){ this->stop(); }); + return invalidated; +} + +bool BackgroundSlicingProcess::invalidate_all_steps() +{ + return m_step_state.invalidate_all(m_step_state_mutex, [this](){ this->stop(); }); +} + }; // namespace Slic3r diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index 758acd682a..4265d1227f 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -6,6 +6,8 @@ #include #include +#include "Print.hpp" + namespace Slic3r { class DynamicPrintConfig; @@ -13,6 +15,11 @@ class GCodePreviewData; class Model; class Print; +// Print step IDs for keeping track of the print state. +enum BackgroundSlicingProcessStep { + bspsGCodeFinalize, bspsCount, +}; + // 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 @@ -32,8 +39,6 @@ public: // The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed. void set_finished_event(int event_id) { m_event_finished_id = event_id; } - // Set the output path of the G-code. - void set_output_path(const std::string &path) { m_output_path = path; } // 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. @@ -46,6 +51,13 @@ public: // 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(const Model &model, const DynamicPrintConfig &config); + // Set the export path of the G-code. + // Once the path is set, the G-code + void schedule_export(const std::string &path); + // Clear m_export_path. + void reset_export(); + // Once the G-code export is scheduled, the apply() methods will do nothing. + bool is_export_scheduled() const { return ! m_export_path.empty(); } 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). @@ -74,8 +86,11 @@ private: Print *m_print = nullptr; // Data structure, to which the G-code export writes its annotations. GCodePreviewData *m_gcode_preview_data = nullptr; + // Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID. std::string m_temp_output_path; - std::string m_output_path; + // Output path provided by the user. The output path may be set even if the slicing is running, + // but once set, it cannot be re-set. + std::string m_export_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; @@ -84,6 +99,14 @@ private: std::condition_variable m_condition; State m_state = STATE_INITIAL; + PrintState m_step_state; + mutable tbb::mutex m_step_state_mutex; + void set_step_started(BackgroundSlicingProcessStep step); + void set_step_done(BackgroundSlicingProcessStep step); + bool is_step_done(BackgroundSlicingProcessStep step) const { return m_step_state.is_done(step); } + bool invalidate_step(BackgroundSlicingProcessStep step); + bool invalidate_all_steps(); + // 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. diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 5db94b95f6..9928e43343 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5145,7 +5145,7 @@ void GLCanvas3D::_render_layer_editing_overlay() const #else int object_idx = int(volume->select_group_id / 1000000); #endif // ENABLE_EXTENDED_SELECTION - if ((int)m_print->objects().size() < object_idx) + if ((int)m_print->objects().size() <= object_idx) return; const PrintObject* print_object = m_print->get_object(object_idx); diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 0e23585f7b..9e18cac020 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -261,18 +261,6 @@ void warning_catcher(wxWindow* parent, const 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(wxGetApp().mainframe, event.Clone()); - }); -} - void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value) { if (comboCtrl == nullptr) diff --git a/src/slic3r/GUI/GUI.hpp b/src/slic3r/GUI/GUI.hpp index 3203fd8b25..cbc20c3c1a 100644 --- a/src/slic3r/GUI/GUI.hpp +++ b/src/slic3r/GUI/GUI.hpp @@ -80,10 +80,6 @@ void show_error_id(int id, const std::string& message); // For Perl void show_info(wxWindow* parent, const wxString& message, const wxString& title); void warning_catcher(wxWindow* parent, const 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); - // Creates a wxCheckListBoxComboPopup inside the given wxComboCtrl, filled with the given text and items. // Items are all initialized to the given value. // Items must be separated by '|', for example "Item1|Item2|Item3", and so on. diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e5668732ac..fa8368b4dc 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -63,10 +63,10 @@ namespace Slic3r { namespace GUI { +wxDEFINE_EVENT(EVT_PROGRESS_BAR, wxCommandEvent); wxDEFINE_EVENT(EVT_SLICING_COMPLETED, wxCommandEvent); wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, wxCommandEvent); - // Sidebar widgets // struct InfoBox : public wxStaticBox @@ -741,9 +741,6 @@ struct Plater::priv std::vector objects; #endif // !ENABLE_EXTENDED_SELECTION - fs::path export_gcode_output_file; - fs::path send_gcode_file; - // GUI elements wxNotebook *notebook; Sidebar *sidebar; @@ -809,7 +806,7 @@ struct Plater::priv void on_notebook_changed(wxBookCtrlEvent&); void on_select_preset(wxCommandEvent&); - void on_progress_event(); + void on_progress_event(wxCommandEvent&); void on_update_print_preview(wxCommandEvent&); void on_process_completed(wxCommandEvent&); void on_layer_editing_toggled(bool enable); @@ -880,6 +877,14 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) : background_process.set_gcode_preview_data(&gcode_preview_data); background_process.set_sliced_event(EVT_SLICING_COMPLETED); background_process.set_finished_event(EVT_PROCESS_COMPLETED); + // Register progress callback from the Print class to the Platter. + print.set_status_callback([this](int percent, const std::string &message){ + wxCommandEvent event(EVT_PROGRESS_BAR); + event.SetInt(percent); + event.SetString(message); + wxQueueEvent(this->q, event.Clone()); + }); + this->q->Bind(EVT_PROGRESS_BAR, &priv::on_progress_event, this); _3DScene::add_canvas(canvas3D); _3DScene::allow_multisample(canvas3D, GLCanvas3DManager::can_multisample()); @@ -902,7 +907,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) : _3DScene::enable_shader(canvas3D, true); _3DScene::enable_force_zoom_to_bed(canvas3D, true); - background_process_timer.Bind(wxEVT_TIMER, [this](wxTimerEvent &evt){ this->async_apply_config(); }, 0); + this->background_process_timer.SetOwner(this->q, 0); + this->q->Bind(wxEVT_TIMER, [this](wxTimerEvent &evt){ this->async_apply_config(); }); auto *bed_shape = config->opt("bed_shape"); _3DScene::set_bed_shape(canvas3D, bed_shape->values); @@ -938,7 +944,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) : canvas3D->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { arrange(); }); #if !ENABLE_EXTENDED_SELECTION canvas3D->Bind(EVT_GLCANVAS_ROTATE_OBJECT, [this](Event &evt) { /*TODO: call rotate */ }); - canvas3D->Bind(EVT_GLCANVAS_SCALE_UNIFORMLY, [this](SimpleEvent&) { scale(); }); + canvas3D->Bind(EVT_GLCANVAS_SCALE_UNIFORMLY, [this](SimpleEvent&) { this->scale(); }); #endif // !ENABLE_EXTENDED_SELECTION canvas3D->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [q](Event &evt) { evt.data == 1 ? q->increase_instances() : q->decrease_instances(); }); canvas3D->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); }); @@ -998,7 +1004,7 @@ void Plater::priv::update(bool force_autocenter) } // stop_background_process(); // TODO - print.reload_model_instances(); +// print.reload_model_instances(); #if !ENABLE_EXTENDED_SELECTION const auto selections = collect_selections(); @@ -1008,7 +1014,7 @@ void Plater::priv::update(bool force_autocenter) preview->reset_gcode_preview_data(); preview->reload_print(); - schedule_background_process(); + this->schedule_background_process(); } void Plater::priv::select_view(const std::string& direction) @@ -1034,7 +1040,7 @@ void Plater::priv::update_ui_from_settings() // } } -ProgressStatusBar* Plater::priv::statusbar() +ProgressStatusBar* Plater::priv::statusbar() { return main_frame->m_statusbar; } @@ -1257,7 +1263,7 @@ std::unique_ptr Plater::priv::get_export_file(GUI::FileType case FT_GCODE: wildcard = file_wildcards[file_type]; break; - +// FT_GCODE default: wildcard = file_wildcards[FT_MODEL]; break; @@ -1290,7 +1296,6 @@ std::unique_ptr Plater::priv::get_export_file(GUI::FileType fs::path path(dlg->GetPath()); wxGetApp().app_config->update_last_output_dir(path.parent_path().string()); - export_gcode_output_file = path; return dlg; } @@ -1446,7 +1451,7 @@ void Plater::priv::object_list_changed() _3DScene::enable_toolbar_item(canvas3D, "arrange", have_objects); #endif // ENABLE_EXTENDED_SELECTION - const bool export_in_progress = !(export_gcode_output_file.empty() && send_gcode_file.empty()); + const bool export_in_progress = this->background_process.is_export_scheduled(); // || ! send_gcode_file.empty()); // XXX: is this right? const bool model_fits = _3DScene::check_volumes_outside_state(canvas3D, config) == ModelInstance::PVS_Inside; @@ -1478,7 +1483,7 @@ void Plater::priv::remove(size_t obj_idx) objects.erase(objects.begin() + obj_idx); #endif // !ENABLE_EXTENDED_SELECTION model.delete_object(obj_idx); - print.delete_object(obj_idx); +// print.delete_object(obj_idx); // Delete object from Sidebar list sidebar->obj_list()->delete_object_from_list(obj_idx); @@ -1504,7 +1509,7 @@ void Plater::priv::reset() objects.clear(); #endif // !ENABLE_EXTENDED_SELECTION model.clear_objects(); - print.clear_objects(); +// print.clear_objects(); // Delete all objects from list on c++ side sidebar->obj_list()->delete_all_objects_from_list(); @@ -1609,7 +1614,6 @@ void Plater::priv::split_object() if (new_objects.size() == 1) { Slic3r::GUI::warning_catcher(q, _(L("The selected object couldn't be split because it contains only one part."))); -// $self->schedule_background_process; } else { @@ -1635,9 +1639,16 @@ void Plater::priv::schedule_background_process() void Plater::priv::async_apply_config() { + DynamicPrintConfig config = wxGetApp().preset_bundle->full_config(); + BoundingBox bed_box_2D = get_extents(Polygon::new_scale(config.opt("bed_shape")->values)); + BoundingBoxf3 print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), scale_(config.opt_float("max_print_height")))); + // Allow the objects to protrude below the print bed, only the part of the object above the print bed will be sliced. + print_volume.min(2) = -1e10; + this->q->model().update_print_volume_state(print_volume); + // Apply new config to the possibly running background task. bool was_running = this->background_process.running(); - bool invalidated = this->background_process.apply(this->q->model(), wxGetApp().preset_bundle->full_config()); + bool invalidated = this->background_process.apply(this->q->model(), std::move(config)); // Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile. if (Slic3r::_3DScene::is_layers_editing_enabled(this->canvas3D)) this->canvas3D->Refresh(); @@ -1669,19 +1680,19 @@ void Plater::priv::async_apply_config() void Plater::priv::start_background_process() { + if (this->background_process.running()) + return; // return if ! @{$self->{objects}} || $self->{background_slicing_process}->running; - // Don't start process thread if config is not valid. - std::string err = wxGetApp().preset_bundle->full_config().validate(); - if (err.empty()) - err = this->q->print().validate(); + // Don't start process thread if Print is not valid. + std::string err = this->q->print().validate(); if (! err.empty()) { - // $self->statusbar->SetStatusText(err); - return; - } - // Copy the names of active presets into the placeholder parser. - wxGetApp().preset_bundle->export_selections(this->q->print().placeholder_parser()); - // Start the background process. - this->background_process.start(); + this->statusbar()->set_status_text(err); + } else { + // Copy the names of active presets into the placeholder parser. + wxGetApp().preset_bundle->export_selections(this->q->print().placeholder_parser()); + // Start the background process. + this->background_process.start(); + } } void Plater::priv::stop_background_process() @@ -1795,9 +1806,10 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) wxGetApp().plater()->on_config_change(wxGetApp().preset_bundle->full_config()); } -void Plater::priv::on_progress_event() +void Plater::priv::on_progress_event(wxCommandEvent &evt) { - // TODO + this->statusbar()->set_progress(evt.GetInt()); + this->statusbar()->set_status_text(evt.GetString() + wxString::FromUTF8("…")); } void Plater::priv::on_update_print_preview(wxCommandEvent &) @@ -1813,62 +1825,25 @@ void Plater::priv::on_update_print_preview(wxCommandEvent &) #endif // !ENABLE_EXTENDED_SELECTION } -void Plater::priv::on_process_completed(wxCommandEvent &) +void Plater::priv::on_process_completed(wxCommandEvent &evt) { // Stop the background task, wait until the thread goes into the "Idle" state. // At this point of time the thread should be either finished or canceled, // so the following call just confirms, that the produced data were consumed. this->background_process.stop(); - //$self->statusbar->ResetCancelCallback(); - //$self->statusbar->StopBusy; - //$self->statusbar->SetStatusText(""); - -/* - my $message; - my $send_gcode = 0; - my $do_print = 0; -# print "Process completed, message: ", $message, "\n"; - if (defined($result)) { - $message = L("Export failed"); - } else { - # G-code file exported successfully. - if ($self->{print_file}) { - $message = L("File added to print queue"); - $do_print = 1; - } elsif ($self->{send_gcode_file}) { - $message = L("Sending G-code file to the Printer Host ..."); - $send_gcode = 1; - } elsif (defined $self->{export_gcode_output_file}) { - $message = L("G-code file exported to ") . $self->{export_gcode_output_file}; - } else { - $message = L("Slicing complete"); - } + this->statusbar()->reset_cancel_callback(); + this->statusbar()->stop_busy(); + + bool success = evt.GetInt(); + // Reset the "export G-code path" name, so that the automatic background processing will be enabled again. + this->background_process.reset_export(); + if (! success) { + wxString message = evt.GetString(); + if (message.IsEmpty()) + message = _(L("Export failed")); + this->statusbar()->set_status_text(message); } - $self->{export_gcode_output_file} = undef; - wxTheApp->notify($message); - - $self->do_print if $do_print; - # Send $self->{send_gcode_file} to OctoPrint. - if ($send_gcode) { - my $host = Slic3r::PrintHost::get_print_host($self->{config}); - if ($host->send_gcode($self->{send_gcode_file})) { - $message = L("Upload to host finished."); - } else { - $message = ""; - } - } -*/ - - // As of now, the BackgroundProcessing thread posts status bar update messages to a queue on the MainFrame.pm, - // but the "Processing finished" message is posted to this window. - // Delay the following status bar update, so it will be called later than what is received by MainFrame.pm. - //wxTheApp->CallAfter(sub { -// $self->statusbar->SetStatusText($message); -// }); - - //$self->{print_file} = undef; - //$self->{send_gcode_file} = undef; //$self->print_info_box_show(1); // this updates buttons status @@ -2209,9 +2184,9 @@ void Plater::increase_instances(size_t num) Vec3d offset_vec = model_instance->get_offset() + Vec3d(offset, offset, 0.0); model_object->add_instance(offset_vec, model_instance->get_scaling_factor(), model_instance->get_rotation()); #if ENABLE_EXTENDED_SELECTION - p->print.get_object(obj_idx)->add_copy(Slic3r::to_2d(offset_vec)); +// p->print.get_object(obj_idx)->add_copy(Slic3r::to_2d(offset_vec)); #else - p->print.get_object(*obj_idx)->add_copy(Slic3r::to_2d(offset_vec)); +// p->print.get_object(*obj_idx)->add_copy(Slic3r::to_2d(offset_vec)); #endif // ENABLE_EXTENDED_SELECTION } @@ -2254,9 +2229,9 @@ void Plater::decrease_instances(size_t num) for (size_t i = 0; i < num; i++) { model_object->delete_last_instance(); #if ENABLE_EXTENDED_SELECTION - p->print.get_object(obj_idx)->delete_last_copy(); +// p->print.get_object(obj_idx)->delete_last_copy(); #else - p->print.get_object(*obj_idx)->delete_last_copy(); +// p->print.get_object(*obj_idx)->delete_last_copy(); #endif // ENABLE_EXTENDED_SELECTION } #if ENABLE_EXTENDED_SELECTION @@ -2281,8 +2256,7 @@ void Plater::decrease_instances(size_t num) #endif // ENABLE_EXTENDED_SELECTION p->selection_changed(); - - // $self->schedule_background_process; + this->p->schedule_background_process(); } void Plater::set_number_of_copies(size_t num) @@ -2307,38 +2281,35 @@ void Plater::set_number_of_copies(size_t num) decrease_instances(-diff); } -fs::path Plater::export_gcode(const fs::path &output_path) +void Plater::export_gcode(fs::path output_path) { #if ENABLE_EXTENDED_SELECTION - if (p->model.objects.empty()) { return ""; } + if (p->model.objects.empty()) + return; #else - if (p->objects.empty()) { return ""; } + if (p->objects.empty()) + return; #endif // ENABLE_EXTENDED_SELECTION - if (! p->export_gcode_output_file.empty()) { + if (this->p->background_process.is_export_scheduled()) { GUI::show_error(this, _(L("Another export job is currently running."))); - return ""; + return; } std::string err = wxGetApp().preset_bundle->full_config().validate(); - if (err.empty()) { + if (err.empty()) err = p->print.validate(); - } if (! err.empty()) { // The config is not valid GUI::show_error(this, _(err)); - return fs::path(); + return; } // Copy the names of active presets into the placeholder parser. wxGetApp().preset_bundle->export_selections(p->print.placeholder_parser()); // select output file - if (! output_path.empty()) { - p->export_gcode_output_file = fs::path(p->print.output_filepath(output_path.string())); - // FIXME: ^ errors to handle? - } else { - + if (output_path.empty()) { // XXX: take output path from CLI opts? Ancient Slic3r versions used to do that... // If possible, remove accents from accented latin characters. @@ -2355,13 +2326,17 @@ fs::path Plater::export_gcode(const fs::path &output_path) wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); - if (dlg.ShowModal() != wxID_OK) { return ""; } - fs::path path(dlg.GetPath()); - wxGetApp().app_config->update_last_output_dir(path.parent_path().string()); - p->export_gcode_output_file = path; + if (dlg.ShowModal() == wxID_OK) { + fs::path path(dlg.GetPath()); + wxGetApp().app_config->update_last_output_dir(path.parent_path().string()); + output_path = path; + } } - return p->export_gcode_output_file; + if (! output_path.empty()) { + this->p->background_process.schedule_export(p->print.output_filepath(output_path.string())); + this->p->background_process.start(); + } } void Plater::export_stl() @@ -2452,7 +2427,7 @@ void Plater::reslice() void Plater::send_gcode() { - p->send_gcode_file = export_gcode(); +// p->send_gcode_file = export_gcode(); } void Plater::on_extruders_change(int num_extruders) diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 544d4672ad..ab912f5e3a 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -118,7 +118,7 @@ public: void set_number_of_copies(size_t num); // Note: empty path means "use the default" - boost::filesystem::path export_gcode(const boost::filesystem::path &output_path = boost::filesystem::path()); + void export_gcode(boost::filesystem::path output_path = boost::filesystem::path()); void export_stl(); void export_amf(); void export_3mf(); diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 1a6063dc73..07410e325f 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -884,7 +884,8 @@ bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui) std::string old_label = ui->GetString(ui_id).utf8_str().data(); std::string preset_name = Preset::remove_suffix_modified(old_label); const Preset *preset = this->find_preset(preset_name, false); - assert(preset != nullptr); +// The old_label could be the "----- system presets ------" or the "------- user presets --------" separator. +// assert(preset != nullptr); if (preset != nullptr) { std::string new_label = preset->is_dirty ? preset->name + g_suffix_modified : preset->name; if (old_label != new_label) diff --git a/src/slic3r/GUI/ProgressStatusBar.cpp b/src/slic3r/GUI/ProgressStatusBar.cpp index 45c3aaa76b..44a7b06c51 100644 --- a/src/slic3r/GUI/ProgressStatusBar.cpp +++ b/src/slic3r/GUI/ProgressStatusBar.cpp @@ -53,8 +53,8 @@ ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id): }); m_cancelbutton->Bind(wxEVT_BUTTON, [this](const wxCommandEvent&) { - if(m_cancel_cb) m_cancel_cb(); - m_perl_cancel_callback.call(); + if (m_cancel_cb) + m_cancel_cb(); m_cancelbutton->Hide(); }); } @@ -136,7 +136,17 @@ void ProgressStatusBar::embed(wxFrame *frame) void ProgressStatusBar::set_status_text(const wxString& txt) { - self->SetStatusText(wxString::FromUTF8(txt.c_str())); + self->SetStatusText(txt); +} + +void ProgressStatusBar::set_status_text(const std::string& txt) +{ + this->set_status_text(txt.c_str()); +} + +void ProgressStatusBar::set_status_text(const char *txt) +{ + this->set_status_text(wxString::FromUTF8(txt)); } void ProgressStatusBar::show_cancel_button() diff --git a/src/slic3r/GUI/ProgressStatusBar.hpp b/src/slic3r/GUI/ProgressStatusBar.hpp index 57ec9ae2bb..f33a70ed30 100644 --- a/src/slic3r/GUI/ProgressStatusBar.hpp +++ b/src/slic3r/GUI/ProgressStatusBar.hpp @@ -4,8 +4,6 @@ #include #include -#include "callback.hpp" - class wxTimer; class wxGauge; class wxButton; @@ -22,7 +20,8 @@ namespace Slic3r { * of the Slicer main window. It consists of a message area to the left and a * progress indication area to the right with an optional cancel button. */ -class ProgressStatusBar { +class ProgressStatusBar +{ wxStatusBar *self; // we cheat! It should be the base class but: perl! wxTimer *m_timer; wxGauge *m_prog; @@ -35,25 +34,26 @@ public: ProgressStatusBar(wxWindow *parent = nullptr, int id = -1); ~ProgressStatusBar(); - int get_progress() const; - void set_progress(int); - int get_range() const; - void set_range(int = 100); - void show_progress(bool); - void start_busy(int = 100); - void stop_busy(); + int get_progress() const; + void set_progress(int); + int get_range() const; + void set_range(int = 100); + void show_progress(bool); + void start_busy(int = 100); + void stop_busy(); inline bool is_busy() const { return m_busy; } - void set_cancel_callback(CancelFn = CancelFn()); - inline void remove_cancel_callback() { set_cancel_callback(); } - void run(int rate); - void embed(wxFrame *frame = nullptr); - void set_status_text(const wxString& txt); + void set_cancel_callback(CancelFn = CancelFn()); + inline void reset_cancel_callback() { set_cancel_callback(); } + void run(int rate); + void embed(wxFrame *frame = nullptr); + void set_status_text(const wxString& txt); + void set_status_text(const std::string& txt); + void set_status_text(const char *txt); // Temporary methods to satisfy Perl side - void show_cancel_button(); - void hide_cancel_button(); + void show_cancel_button(); + void hide_cancel_button(); - PerlCallback m_perl_cancel_callback; private: bool m_busy = false; CancelFn m_cancel_cb; diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 0424d8b7b2..39708ca7a9 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -185,22 +185,6 @@ _constant() } %}; - 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()); - } - %}; - void export_png(char *path) %code%{ try { THIS->export_png(path);