mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 12:41:20 -06:00 
			
		
		
		
	Merge branch 'master' of https://github.com/Prusa3d/Slic3r
This commit is contained in:
		
						commit
						1e325f8374
					
				
					 28 changed files with 447 additions and 257 deletions
				
			
		
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB | 
|  | @ -545,8 +545,8 @@ public: | |||
|     _NofitPolyPlacer& operator=(const _NofitPolyPlacer&) = default; | ||||
| 
 | ||||
| #ifndef BP2D_COMPILER_MSVC12 // MSVC2013 does not support default move ctors
 | ||||
|     _NofitPolyPlacer(_NofitPolyPlacer&&) BP2D_NOEXCEPT = default; | ||||
|     _NofitPolyPlacer& operator=(_NofitPolyPlacer&&) BP2D_NOEXCEPT = default; | ||||
|     _NofitPolyPlacer(_NofitPolyPlacer&&) /*BP2D_NOEXCEPT*/ = default; | ||||
|     _NofitPolyPlacer& operator=(_NofitPolyPlacer&&) /*BP2D_NOEXCEPT*/ = default; | ||||
| #endif | ||||
| 
 | ||||
|     static inline double overfit(const Box& bb, const RawShape& bin) { | ||||
|  |  | |||
|  | @ -77,8 +77,7 @@ public: | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Unlocked observers/hints
 | ||||
|     // Thread unsafe! Keep in mind you need to re-verify the result after locking!
 | ||||
|     // Unlocked observer/hint. Thread unsafe! Keep in mind you need to re-verify the result after locking.
 | ||||
|     size_t size_hint() const noexcept { return m_queue.size(); } | ||||
| 
 | ||||
|     LockedConstPtr lock_read() const | ||||
|  |  | |||
|  | @ -135,11 +135,6 @@ objfunc(const PointImpl& bincenter, | |||
|         const ItemGroup& remaining | ||||
|         ) | ||||
| { | ||||
|     using Coord = TCoord<PointImpl>; | ||||
| 
 | ||||
|     static const double ROUNDNESS_RATIO = 0.5; | ||||
|     static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; | ||||
| 
 | ||||
|     // We will treat big items (compared to the print bed) differently
 | ||||
|     auto isBig = [bin_area](double a) { | ||||
|         return a/bin_area > BIG_ITEM_TRESHOLD ; | ||||
|  | @ -629,11 +624,12 @@ BedShapeHint bedShape(const Polyline &bed) { | |||
|         avg_dist /= vertex_distances.size(); | ||||
| 
 | ||||
|         Circle ret(center, avg_dist); | ||||
|         for (auto el: vertex_distances) | ||||
|         for(auto el : vertex_distances) | ||||
|         { | ||||
|             if (abs(el - avg_dist) > 10 * SCALED_EPSILON) | ||||
|             if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) { | ||||
|                 ret = Circle(); | ||||
|             break; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return ret; | ||||
|  | @ -665,8 +661,6 @@ bool arrange(Model &model, | |||
|              std::function<void (unsigned)> progressind, | ||||
|              std::function<bool ()> stopcondition) | ||||
| { | ||||
|     using ArrangeResult = _IndexedPackGroup<PolygonImpl>; | ||||
| 
 | ||||
|     bool ret = true; | ||||
| 
 | ||||
|     // Get the 2D projected shapes with their 3D model instance pointers
 | ||||
|  |  | |||
|  | @ -722,6 +722,10 @@ public: | |||
|         return m_pad; | ||||
|     } | ||||
| 
 | ||||
|     void remove_pad() { | ||||
|         m_pad = Pad(); | ||||
|     } | ||||
| 
 | ||||
|     const Pad& pad() const { return m_pad; } | ||||
| 
 | ||||
|     // WITHOUT THE PAD!!!
 | ||||
|  | @ -1729,6 +1733,11 @@ const TriangleMesh &SLASupportTree::get_pad() const | |||
|     return m_impl->pad().tmesh; | ||||
| } | ||||
| 
 | ||||
| void SLASupportTree::remove_pad() | ||||
| { | ||||
|     m_impl->remove_pad(); | ||||
| } | ||||
| 
 | ||||
| SLASupportTree::SLASupportTree(const PointSet &points, | ||||
|                                const EigenMesh3D& emesh, | ||||
|                                const SupportConfig &cfg, | ||||
|  |  | |||
|  | @ -164,6 +164,8 @@ public: | |||
|     /// Get the pad geometry
 | ||||
|     const TriangleMesh& get_pad() const; | ||||
| 
 | ||||
|     void remove_pad(); | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -107,7 +107,8 @@ PointSet normals(const PointSet& points, const EigenMesh3D& emesh, | |||
|     // structure
 | ||||
|     EigenMesh3D  mesh; | ||||
|     Eigen::VectorXi SVI, SVJ; | ||||
|     igl::remove_duplicate_vertices(emesh.V, emesh.F, 1e-6, | ||||
|     static const double dEPS = 1e-6; | ||||
|     igl::remove_duplicate_vertices(emesh.V, emesh.F, dEPS, | ||||
|                                    mesh.V, SVI, SVJ, mesh.F); | ||||
| 
 | ||||
|     igl::point_mesh_squared_distance( points, mesh.V, mesh.F, dists, I, C); | ||||
|  | @ -155,6 +156,7 @@ PointSet normals(const PointSet& points, const EigenMesh3D& emesh, | |||
|             ia = trindex(0); ib = trindex(2); | ||||
|         } | ||||
| 
 | ||||
|         // vector for the neigboring triangles including the detected one.
 | ||||
|         std::vector<Vec3i> neigh; | ||||
|         if(ic >= 0) { // The point is right on a vertex of the triangle
 | ||||
|             for(int n = 0; n < mesh.F.rows(); ++n) { | ||||
|  | @ -175,17 +177,32 @@ PointSet normals(const PointSet& points, const EigenMesh3D& emesh, | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if(!neigh.empty()) { // there were neighbors to count with
 | ||||
|         // Calculate the normals for the neighboring triangles
 | ||||
|         std::vector<Vec3d> neighnorms; neighnorms.reserve(neigh.size()); | ||||
|         for(const Vec3i& tri : neigh) { | ||||
|             const Vec3d& pt1 = mesh.V.row(tri(0)); | ||||
|             const Vec3d& pt2 = mesh.V.row(tri(1)); | ||||
|             const Vec3d& pt3 = mesh.V.row(tri(2)); | ||||
|             Eigen::Vector3d U = pt2 - pt1; | ||||
|             Eigen::Vector3d V = pt3 - pt1; | ||||
|             neighnorms.emplace_back(U.cross(V).normalized()); | ||||
|         } | ||||
| 
 | ||||
|         // Throw out duplicates. They would case trouble with summing.
 | ||||
|         auto lend = std::unique(neighnorms.begin(), neighnorms.end(), | ||||
|                                 [](const Vec3d& n1, const Vec3d& n2) { | ||||
|             // Compare normals for equivalence. This is controvers stuff.
 | ||||
|             // We will go for the third significant digit precision.
 | ||||
|             auto deq = [](double a, double b) { return std::abs(a-b) < 1e-3; }; | ||||
|             return deq(n1(X), n2(X)) && deq(n1(Y), n2(Y)) && deq(n1(Z), n2(Z)); | ||||
|         }); | ||||
| 
 | ||||
|         if(!neighnorms.empty()) { // there were neighbors to count with
 | ||||
|             // sum up the normals and then normalize the result again.
 | ||||
|             // This unification seems to be enough.
 | ||||
|             Vec3d sumnorm(0, 0, 0); | ||||
|             for(const Vec3i& tri : neigh) { | ||||
|                 const Vec3d& pt1 = mesh.V.row(tri(0)); | ||||
|                 const Vec3d& pt2 = mesh.V.row(tri(1)); | ||||
|                 const Vec3d& pt3 = mesh.V.row(tri(2)); | ||||
|                 Eigen::Vector3d U = pt2 - pt1; | ||||
|                 Eigen::Vector3d V = pt3 - pt1; | ||||
|                 sumnorm += U.cross(V).normalized(); | ||||
|             } | ||||
|             sumnorm /= neigh.size(); | ||||
|             sumnorm = std::accumulate(neighnorms.begin(), lend, sumnorm); | ||||
|             sumnorm.normalize(); | ||||
|             ret.row(i) = sumnorm; | ||||
|         } | ||||
|         else { // point lies safely within its triangle
 | ||||
|  |  | |||
|  | @ -560,9 +560,13 @@ void SLAPrint::process() | |||
|         // and before the supports had been sliced. (or the slicing has to be
 | ||||
|         // repeated)
 | ||||
| 
 | ||||
|         if(po.m_config.pad_enable.getBool() && | ||||
|            po.m_supportdata && | ||||
|            po.m_supportdata->support_tree_ptr) | ||||
|         if(!po.m_supportdata || !po.m_supportdata->support_tree_ptr) { | ||||
|             BOOST_LOG_TRIVIAL(warning) << "Uninitialized support data at " | ||||
|                                        << "pad creation."; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if(po.m_config.pad_enable.getBool()) | ||||
|         { | ||||
|             double wt = po.m_config.pad_wall_thickness.getFloat(); | ||||
|             double h =  po.m_config.pad_wall_height.getFloat(); | ||||
|  | @ -586,6 +590,8 @@ void SLAPrint::process() | |||
| 
 | ||||
|             pcfg.throw_on_cancel = thrfn; | ||||
|             po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg); | ||||
|         } else { | ||||
|             po.m_supportdata->support_tree_ptr->remove_pad(); | ||||
|         } | ||||
| 
 | ||||
|         po.throw_if_canceled(); | ||||
|  | @ -866,6 +872,7 @@ void SLAPrint::process() | |||
|             if(po->m_stepmask[currentstep] && po->set_started(currentstep)) { | ||||
|                 report_status(*this, int(st), OBJ_STEP_LABELS[currentstep]); | ||||
|                 pobj_program[currentstep](*po); | ||||
|                 throw_if_canceled(); | ||||
|                 po->set_done(currentstep); | ||||
|             } | ||||
| 
 | ||||
|  | @ -891,6 +898,7 @@ void SLAPrint::process() | |||
|         { | ||||
|             report_status(*this, int(st), PRINT_STEP_LABELS[currentstep]); | ||||
|             print_program[currentstep](); | ||||
|             throw_if_canceled(); | ||||
|             set_done(currentstep); | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1920,8 +1920,7 @@ void GLModel::render() const | |||
|     if (m_useVBOs) | ||||
|         render_VBOs(); | ||||
|     else | ||||
|     { | ||||
|     } | ||||
|         render_legacy(); | ||||
| } | ||||
| 
 | ||||
| void GLModel::render_VBOs() const | ||||
|  | @ -1949,6 +1948,25 @@ void GLModel::render_VBOs() const | |||
|     ::glDisable(GL_BLEND); | ||||
| } | ||||
| 
 | ||||
| void GLModel::render_legacy() const | ||||
| { | ||||
|     ::glEnable(GL_LIGHTING); | ||||
|     ::glEnable(GL_BLEND); | ||||
|     ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | ||||
| 
 | ||||
|     ::glCullFace(GL_BACK); | ||||
|     ::glEnableClientState(GL_VERTEX_ARRAY); | ||||
|     ::glEnableClientState(GL_NORMAL_ARRAY); | ||||
| 
 | ||||
|     m_volume.render_legacy(); | ||||
| 
 | ||||
|     ::glDisableClientState(GL_VERTEX_ARRAY); | ||||
|     ::glDisableClientState(GL_NORMAL_ARRAY); | ||||
| 
 | ||||
|     ::glDisable(GL_BLEND); | ||||
|     ::glDisable(GL_LIGHTING); | ||||
| } | ||||
| 
 | ||||
| bool GLArrow::on_init(bool useVBOs) | ||||
| { | ||||
|     Pointf3s vertices; | ||||
|  |  | |||
|  | @ -611,6 +611,7 @@ protected: | |||
| 
 | ||||
| private: | ||||
|     void render_VBOs() const; | ||||
|     void render_legacy() const; | ||||
| }; | ||||
| 
 | ||||
| class GLArrow : public GLModel | ||||
|  |  | |||
|  | @ -84,26 +84,7 @@ void BackgroundSlicingProcess::process_fff() | |||
| 	    	run_post_process_scripts(export_path, m_fff_print->config()); | ||||
| 	    	m_print->set_status(100, "G-code file exported to " + export_path); | ||||
| 	    } else if (! m_upload_job.empty()) { | ||||
| 			// A print host upload job has been scheduled
 | ||||
| 
 | ||||
| 	    	// XXX: is fs::path::string() right?
 | ||||
| 
 | ||||
| 			// Generate a unique temp path to which the gcode is copied
 | ||||
| 			boost::filesystem::path source_path = boost::filesystem::temp_directory_path() | ||||
| 				/ boost::filesystem::unique_path(".printhost.%%%%-%%%%-%%%%-%%%%.gcode"); | ||||
| 
 | ||||
| 			if (copy_file(m_temp_output_path, source_path.string()) != 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(source_path.string(), m_fff_print->config()); | ||||
| 			m_print->set_status(100, (boost::format("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue") % m_upload_job.printhost->get_host()).str()); | ||||
| 
 | ||||
| 			m_upload_job.upload_data.source_path = std::move(source_path); | ||||
| 			m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); | ||||
| 
 | ||||
| 			GUI::wxGetApp().printhost_job_queue().enqueue(std::move(m_upload_job)); | ||||
| 			prepare_upload(); | ||||
| 	    } else { | ||||
| 	    	m_print->set_status(100, "Slicing complete"); | ||||
| 	    } | ||||
|  | @ -170,6 +151,10 @@ void BackgroundSlicingProcess::process_sla() | |||
|         if (! m_export_path.empty()) { | ||||
|             m_sla_print->export_raster<SLAZipFmt>(m_export_path); | ||||
|             m_print->set_status(100, "Zip file exported to " + m_export_path); | ||||
|         } else if (! m_upload_job.empty()) { | ||||
|             prepare_upload(); | ||||
|         } else { | ||||
|             m_print->set_status(100, "Slicing complete"); | ||||
|         } | ||||
|         this->set_step_done(bspsGCodeFinalize); | ||||
|     } | ||||
|  | @ -440,4 +425,35 @@ bool BackgroundSlicingProcess::invalidate_all_steps() | |||
| 	return m_step_state.invalidate_all([this](){ this->stop_internal(); }); | ||||
| } | ||||
| 
 | ||||
| void BackgroundSlicingProcess::prepare_upload() | ||||
| { | ||||
| 	// A print host upload job has been scheduled, enqueue it to the printhost job queue
 | ||||
| 
 | ||||
| 	// XXX: is fs::path::string() right?
 | ||||
| 
 | ||||
| 	// Generate a unique temp path to which the gcode/zip file is copied/exported
 | ||||
| 	boost::filesystem::path source_path = boost::filesystem::temp_directory_path() | ||||
| 		/ boost::filesystem::unique_path(".printhost.%%%%-%%%%-%%%%-%%%%.gcode"); | ||||
| 
 | ||||
| 	if (m_print == m_fff_print) { | ||||
| 		m_print->set_status(95, "Running post-processing scripts"); | ||||
| 		run_post_process_scripts(source_path.string(), m_fff_print->config()); | ||||
| 
 | ||||
| 		if (copy_file(m_temp_output_path, source_path.string()) != 0) { | ||||
| 			throw std::runtime_error("Copying of the temporary G-code to the output G-code failed"); | ||||
| 		} | ||||
| 
 | ||||
| 		m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); | ||||
| 	} else { | ||||
| 		m_sla_print->export_raster<SLAZipFmt>(source_path.string()); | ||||
| 		// TODO: Also finalize upload path like with FFF when there are statistics for SLA print
 | ||||
| 	} | ||||
| 
 | ||||
| 	m_print->set_status(100, (boost::format("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue") % m_upload_job.printhost->get_host()).str()); | ||||
| 
 | ||||
| 	m_upload_job.upload_data.source_path = std::move(source_path); | ||||
| 
 | ||||
| 	GUI::wxGetApp().printhost_job_queue().enqueue(std::move(m_upload_job)); | ||||
| } | ||||
| 
 | ||||
| }; // namespace Slic3r
 | ||||
|  |  | |||
|  | @ -167,6 +167,7 @@ private: | |||
|     bool                invalidate_all_steps(); | ||||
|     // If the background processing stop was requested, throw CanceledException.
 | ||||
|     void                throw_if_canceled() const { if (m_print->canceled()) throw CanceledException(); } | ||||
|     void                prepare_upload(); | ||||
| 
 | ||||
| 	// 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_slicing_completed_id 	= 0; | ||||
|  |  | |||
|  | @ -3830,7 +3830,6 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) | |||
|     , m_legend_texture_enabled(false) | ||||
|     , m_picking_enabled(false) | ||||
|     , m_moving_enabled(false) | ||||
|     , m_shader_enabled(false) | ||||
|     , m_dynamic_background_enabled(false) | ||||
|     , m_multisample_allowed(false) | ||||
|     , m_regenerate_volumes(true) | ||||
|  | @ -4144,11 +4143,6 @@ void GLCanvas3D::enable_toolbar(bool enable) | |||
|     m_toolbar.set_enabled(enable); | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::enable_shader(bool enable) | ||||
| { | ||||
|     m_shader_enabled = enable; | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::enable_force_zoom_to_bed(bool enable) | ||||
| { | ||||
|     m_force_zoom_to_bed_enabled = enable; | ||||
|  | @ -6322,9 +6316,7 @@ void GLCanvas3D::_render_objects() const | |||
|     ::glEnable(GL_LIGHTING); | ||||
|     ::glEnable(GL_DEPTH_TEST); | ||||
| 
 | ||||
|     if (!m_shader_enabled) | ||||
|         _render_volumes(false); | ||||
|     else if (m_use_VBOs) | ||||
|     if (m_use_VBOs) | ||||
|     { | ||||
|         if (m_picking_enabled) | ||||
|         { | ||||
|  |  | |||
|  | @ -850,7 +850,6 @@ private: | |||
|     bool m_legend_texture_enabled; | ||||
|     bool m_picking_enabled; | ||||
|     bool m_moving_enabled; | ||||
|     bool m_shader_enabled; | ||||
|     bool m_dynamic_background_enabled; | ||||
|     bool m_multisample_allowed; | ||||
|     bool m_regenerate_volumes; | ||||
|  | @ -950,7 +949,6 @@ public: | |||
|     void enable_moving(bool enable); | ||||
|     void enable_gizmos(bool enable); | ||||
|     void enable_toolbar(bool enable); | ||||
|     void enable_shader(bool enable); | ||||
|     void enable_force_zoom_to_bed(bool enable); | ||||
|     void enable_dynamic_background(bool enable); | ||||
|     void allow_multisample(bool allow); | ||||
|  |  | |||
|  | @ -70,7 +70,6 @@ bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, Ba | |||
|     m_canvas->set_config(config); | ||||
|     m_canvas->enable_gizmos(true); | ||||
|     m_canvas->enable_toolbar(true); | ||||
|     m_canvas->enable_shader(true); | ||||
|     m_canvas->enable_force_zoom_to_bed(true); | ||||
| 
 | ||||
| #if !ENABLE_IMGUI | ||||
|  | @ -257,7 +256,6 @@ bool Preview::init(wxNotebook* notebook, DynamicPrintConfig* config, BackgroundS | |||
| 	_3DScene::add_canvas(m_canvas_widget); | ||||
| 	m_canvas = _3DScene::get_canvas(this->m_canvas_widget); | ||||
|     m_canvas->allow_multisample(GLCanvas3DManager::can_multisample()); | ||||
|     m_canvas->enable_shader(true); | ||||
|     m_canvas->set_config(m_config); | ||||
|     m_canvas->set_process(process); | ||||
|     m_canvas->enable_legend_texture(true); | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ | |||
| #include "libslic3r/Utils.hpp" | ||||
| #include "GUI.hpp" | ||||
| #include <wx/scrolwin.h> | ||||
| #include "GUI_App.hpp" | ||||
| 
 | ||||
| namespace Slic3r {  | ||||
| namespace GUI { | ||||
|  | @ -19,41 +20,46 @@ KBShortcutsDialog::KBShortcutsDialog() | |||
| 
 | ||||
|     // fonts
 | ||||
|     wxFont head_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold(); | ||||
|     head_font.SetPointSize(19); | ||||
| 
 | ||||
|     wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); | ||||
|     font.SetPointSize(10); | ||||
|     wxFont bold_font = font.Bold(); | ||||
| #ifdef __WXOSX__ | ||||
|     font.SetPointSize(12); | ||||
|     bold_font.SetPointSize(14); | ||||
| #endif /*__WXOSX__*/ | ||||
|     head_font.SetPointSize(14); | ||||
| #else | ||||
|     head_font.SetPointSize(12); | ||||
| #endif // __WXOSX__
 | ||||
| 
 | ||||
|     const wxFont& font = wxGetApp().small_font(); | ||||
|     const wxFont& bold_font = wxGetApp().bold_font(); | ||||
| 
 | ||||
|     fill_shortcuts(); | ||||
| 
 | ||||
|     auto panel = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(500, 600)); | ||||
|     panel->SetScrollbars(0, 20, 1, 2); | ||||
|     auto  sizer = new wxBoxSizer(wxVERTICAL); | ||||
|     panel->SetSizer(sizer); | ||||
|     auto panel = new wxPanel(this); | ||||
|     auto main_grid_sizer = new wxFlexGridSizer(2, 10, 10); | ||||
|     panel->SetSizer(main_grid_sizer); | ||||
|     main_sizer->Add(panel, 1, wxEXPAND | wxALL, 0); | ||||
| 
 | ||||
|     wxBoxSizer* l_sizer = new wxBoxSizer(wxVERTICAL); | ||||
|     main_grid_sizer->Add(l_sizer, 0); | ||||
| 
 | ||||
|     wxBoxSizer* r_sizer = new wxBoxSizer(wxVERTICAL); | ||||
|     main_grid_sizer->Add(r_sizer, 0); | ||||
| 
 | ||||
|     for (auto& sc : m_full_shortcuts) | ||||
|     { | ||||
|         auto sizer = sc.first == _(L("Main Shortcuts")) ? l_sizer : r_sizer; | ||||
|         wxBoxSizer* hsizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|         sizer->Add(hsizer, 0, wxEXPAND | wxTOP, 25); | ||||
|         sizer->Add(hsizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10); | ||||
| 
 | ||||
|         // logo
 | ||||
|         auto *logo = new wxStaticBitmap(panel, wxID_ANY, logo_bmp); | ||||
|         hsizer->Add(logo, 0, wxEXPAND | wxLEFT | wxRIGHT, 15); | ||||
| 
 | ||||
|         // head
 | ||||
|         wxStaticText* head = new wxStaticText(panel, wxID_ANY, sc.first, wxDefaultPosition, wxSize(400,-1)); | ||||
|         wxStaticText* head = new wxStaticText(panel, wxID_ANY, sc.first, wxDefaultPosition, wxSize(200,-1)); | ||||
|         head->SetFont(head_font); | ||||
|         hsizer->Add(head, 0, wxALIGN_CENTER_VERTICAL); | ||||
| 
 | ||||
|         // Shortcuts list
 | ||||
|         auto grid_sizer = new wxFlexGridSizer(2, 10, 25); | ||||
|         sizer->Add(grid_sizer, 0, wxEXPAND | wxLEFT | wxTOP, 10); | ||||
|         auto grid_sizer = new wxFlexGridSizer(2, 5, 15); | ||||
|         sizer->Add(grid_sizer, 0, wxEXPAND | wxLEFT| wxRIGHT, 15); | ||||
| 
 | ||||
|         for (auto pair : sc.second) | ||||
|         { | ||||
|  | @ -69,9 +75,9 @@ KBShortcutsDialog::KBShortcutsDialog() | |||
|    | ||||
|     wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK); | ||||
| 
 | ||||
|     this->SetEscapeId(wxID_CLOSE); | ||||
|     this->SetEscapeId(wxID_OK); | ||||
|     this->Bind(wxEVT_BUTTON, &KBShortcutsDialog::onCloseDialog, this, wxID_OK); | ||||
|     main_sizer->Add(buttons, 0, wxEXPAND | wxALL, 15); | ||||
|     main_sizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 15); | ||||
|      | ||||
|     this->Bind(wxEVT_LEFT_DOWN, &KBShortcutsDialog::onCloseDialog, this); | ||||
| 
 | ||||
|  | @ -81,32 +87,40 @@ KBShortcutsDialog::KBShortcutsDialog() | |||
| 
 | ||||
| void KBShortcutsDialog::fill_shortcuts() | ||||
| { | ||||
| #ifdef __WXOSX__ | ||||
|     const std::string ctrl = "Cmd+";    // #ys_FIXME_cmd_smb    // Change it for the accorded symbol
 | ||||
|     const std::string alt = "Alt+";     // #ys_FIXME_cmd_smb    // Change it for the accorded symbol
 | ||||
| #else | ||||
|     const std::string ctrl = "Ctrl+"; | ||||
|     const std::string alt = "Alt+"; | ||||
| #endif // __WXOSX__
 | ||||
| 
 | ||||
|     Shortcuts main_shortcuts; | ||||
|     main_shortcuts.reserve(25); | ||||
| 
 | ||||
|     main_shortcuts.push_back(Shortcut("Ctrl+O",         L("Open project STL/OBJ/AMF/3MF with config, delete bed"))); | ||||
|     main_shortcuts.push_back(Shortcut("Ctrl+I",         L("Import STL//OBJ/AMF/3MF without config, keep bed"))); | ||||
|     main_shortcuts.push_back(Shortcut("Ctrl+L",         L("Load Config from .ini/amf/3mf/gcode"))); | ||||
|     main_shortcuts.push_back(Shortcut("Ctrl+Alt+L",     L("Load Config from .ini/amf/3mf/gcode and merge"))); | ||||
|     main_shortcuts.push_back(Shortcut("Ctrl+G",         L("Export Gcode"))); | ||||
|     main_shortcuts.push_back(Shortcut("Ctrl+S",         L("Save project (3MF)"))); | ||||
|     main_shortcuts.push_back(Shortcut("Ctrl+R",         L("(Re)slice"))); | ||||
|     main_shortcuts.push_back(Shortcut("Ctrl+U",         L("Quick slice"))); | ||||
|     main_shortcuts.push_back(Shortcut("Ctrl+Alt+U",     L("Quick slice and Save as"))); | ||||
|     main_shortcuts.push_back(Shortcut("Ctrl+Shift+U",   L("Repeat last quick slice"))); | ||||
|     main_shortcuts.push_back(Shortcut("Ctrl+1",         L("Select Plater Tab"))); | ||||
|     main_shortcuts.push_back(Shortcut("Ctrl+2",         L("Select Print Settings Tab"))); | ||||
|     main_shortcuts.push_back(Shortcut("Ctrl+3",         L("Select Filament Setting Tab"))); | ||||
|     main_shortcuts.push_back(Shortcut("Ctrl+4",         L("Select Printer Setting Tab"))); | ||||
|     main_shortcuts.push_back(Shortcut("Ctrl+5",         L("Switch to 3D"))); | ||||
|     main_shortcuts.push_back(Shortcut("Ctrl+6",         L("Switch to Preview"))); | ||||
|     main_shortcuts.push_back(Shortcut("Ctrl+P",         L("Preferences"))); | ||||
|     main_shortcuts.push_back(Shortcut("0-6",            L("Camera view "))); | ||||
|     main_shortcuts.push_back(Shortcut("+",              L("Add Instance to selected object "))); | ||||
|     main_shortcuts.push_back(Shortcut("-",              L("Remove Instance from selected object"))); | ||||
|     main_shortcuts.push_back(Shortcut("?",              L("Show keyboard shortcuts list"))); | ||||
|     main_shortcuts.push_back(Shortcut("PgUp/PgDn",      L("Switch between 3D and Preview"))); | ||||
|     main_shortcuts.push_back(Shortcut("Shift+LeftMouse",L("Select multiple object/Move multiple object"))); | ||||
|     main_shortcuts.push_back(Shortcut(ctrl+"O"          ,L("Open project STL/OBJ/AMF/3MF with config, delete bed"))); | ||||
|     main_shortcuts.push_back(Shortcut(ctrl+"I"          ,L("Import STL//OBJ/AMF/3MF without config, keep bed"))); | ||||
|     main_shortcuts.push_back(Shortcut(ctrl+"L"          ,L("Load Config from .ini/amf/3mf/gcode"))); | ||||
|     main_shortcuts.push_back(Shortcut(ctrl+"G"          ,L("Export Gcode"))); | ||||
|     main_shortcuts.push_back(Shortcut(ctrl+"S"          ,L("Save project (3MF)"))); | ||||
|     main_shortcuts.push_back(Shortcut(ctrl+alt+"L"      ,L("Load Config from .ini/amf/3mf/gcode and merge"))); | ||||
|     main_shortcuts.push_back(Shortcut(ctrl+"R"          ,L("(Re)slice"))); | ||||
|     main_shortcuts.push_back(Shortcut(ctrl+"U"          ,L("Quick slice"))); | ||||
|     main_shortcuts.push_back(Shortcut(ctrl+"Shift+U"    ,L("Repeat last quick slice"))); | ||||
|     main_shortcuts.push_back(Shortcut(ctrl+"1"          ,L("Select Plater Tab"))); | ||||
|     main_shortcuts.push_back(Shortcut(ctrl+alt+"U"      ,L("Quick slice and Save as"))); | ||||
|     main_shortcuts.push_back(Shortcut(ctrl+"2"          ,L("Select Print Settings Tab"))); | ||||
|     main_shortcuts.push_back(Shortcut(ctrl+"3"          ,L("Select Filament Setting Tab"))); | ||||
|     main_shortcuts.push_back(Shortcut(ctrl+"4"          ,L("Select Printer Setting Tab"))); | ||||
|     main_shortcuts.push_back(Shortcut(ctrl+"5"          ,L("Switch to 3D"))); | ||||
|     main_shortcuts.push_back(Shortcut(ctrl+"6"          ,L("Switch to Preview"))); | ||||
|     main_shortcuts.push_back(Shortcut(ctrl+"P"          ,L("Preferences"))); | ||||
|     main_shortcuts.push_back(Shortcut("0-6"             ,L("Camera view "))); | ||||
|     main_shortcuts.push_back(Shortcut("+"               ,L("Add Instance to selected object "))); | ||||
|     main_shortcuts.push_back(Shortcut("-"               ,L("Remove Instance from selected object"))); | ||||
|     main_shortcuts.push_back(Shortcut("?"               ,L("Show keyboard shortcuts list"))); | ||||
|     main_shortcuts.push_back(Shortcut("PgUp/PgDn"       ,L("Switch between 3D and Preview"))); | ||||
|     main_shortcuts.push_back(Shortcut("Shift+LeftMouse" ,L("Select multiple object/Move multiple object"))); | ||||
| 
 | ||||
|     m_full_shortcuts.emplace(_(L("Main Shortcuts")),    main_shortcuts); | ||||
| 
 | ||||
|  | @ -115,9 +129,9 @@ void KBShortcutsDialog::fill_shortcuts() | |||
|     plater_shortcuts.reserve(20); | ||||
| 
 | ||||
|     plater_shortcuts.push_back(Shortcut("A",        L("Arrange"))); | ||||
|     plater_shortcuts.push_back(Shortcut("Ctrl+A",   L("Select All objects"))); | ||||
|     plater_shortcuts.push_back(Shortcut(ctrl+"A",   L("Select All objects"))); | ||||
|     plater_shortcuts.push_back(Shortcut("Del",      L("Delete selected"))); | ||||
|     plater_shortcuts.push_back(Shortcut("Ctrl+Del", L("Delete all"))); | ||||
|     plater_shortcuts.push_back(Shortcut(ctrl+"Del", L("Delete all"))); | ||||
|     plater_shortcuts.push_back(Shortcut("M",        L("Gizmo move"))); | ||||
|     plater_shortcuts.push_back(Shortcut("S",        L("Gizmo scale"))); | ||||
|     plater_shortcuts.push_back(Shortcut("R",        L("Gizmo rotate"))); | ||||
|  |  | |||
|  | @ -376,7 +376,7 @@ void MainFrame::init_menubar() | |||
| 
 | ||||
|         windowMenu->AppendSeparator(); | ||||
|         append_menu_item(windowMenu, wxID_ANY, L("Print Host Upload Queue"), L("Display the Print Host Upload Queue window"), | ||||
|             [this](wxCommandEvent&) { m_printhost_queue_dlg->ShowModal(); }, "arrow_up.png"); | ||||
|             [this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "arrow_up.png"); | ||||
|     } | ||||
| 
 | ||||
|     // View menu
 | ||||
|  |  | |||
|  | @ -1132,7 +1132,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
|     this->canvas3D->set_config(config); | ||||
|     this->canvas3D->enable_gizmos(true); | ||||
|     this->canvas3D->enable_toolbar(true); | ||||
|     this->canvas3D->enable_shader(true); | ||||
|     this->canvas3D->enable_force_zoom_to_bed(true); | ||||
| #endif // ENABLE_REMOVE_TABS_FROM_PLATER
 | ||||
| 
 | ||||
|  | @ -1162,9 +1161,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
|     hsizer->Add(sidebar, 0, wxEXPAND | wxLEFT | wxRIGHT, 0); | ||||
|     q->SetSizer(hsizer); | ||||
| 
 | ||||
| #if ENABLE_REMOVE_TABS_FROM_PLATER | ||||
|     set_current_panel(view3D); | ||||
| #endif // ENABLE_REMOVE_TABS_FROM_PLATER
 | ||||
| //#if ENABLE_REMOVE_TABS_FROM_PLATER
 | ||||
| //    set_current_panel(view3D);
 | ||||
| //#endif // ENABLE_REMOVE_TABS_FROM_PLATER
 | ||||
| 
 | ||||
|     init_object_menu(); | ||||
| 
 | ||||
|  | @ -1251,6 +1250,10 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
| 
 | ||||
|     update_ui_from_settings(); | ||||
|     q->Layout(); | ||||
| 
 | ||||
| #if ENABLE_REMOVE_TABS_FROM_PLATER | ||||
|     set_current_panel(view3D); | ||||
| #endif // ENABLE_REMOVE_TABS_FROM_PLATER
 | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::update(bool force_full_scene_refresh) | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ | |||
| #include <wx/debug.h> | ||||
| 
 | ||||
| #include "GUI.hpp" | ||||
| #include "GUI_App.hpp" | ||||
| #include "MsgDialog.hpp" | ||||
| #include "I18N.hpp" | ||||
| #include "../Utils/PrintHost.hpp" | ||||
|  | @ -59,7 +60,8 @@ bool PrintHostSendDialog::start_print() const | |||
| 
 | ||||
| 
 | ||||
| wxDEFINE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event); | ||||
| wxDEFINE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event); | ||||
| wxDEFINE_EVENT(EVT_PRINTHOST_ERROR,    PrintHostQueueDialog::Event); | ||||
| wxDEFINE_EVENT(EVT_PRINTHOST_CANCEL,   PrintHostQueueDialog::Event); | ||||
| 
 | ||||
| PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id) | ||||
|     : wxEvent(winid, eventType) | ||||
|  | @ -87,6 +89,7 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) | |||
|     : wxDialog(parent, wxID_ANY, _(L("Print host upload queue")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) | ||||
|     , on_progress_evt(this, EVT_PRINTHOST_PROGRESS, &PrintHostQueueDialog::on_progress, this) | ||||
|     , on_error_evt(this, EVT_PRINTHOST_ERROR, &PrintHostQueueDialog::on_error, this) | ||||
|     , on_cancel_evt(this, EVT_PRINTHOST_CANCEL, &PrintHostQueueDialog::on_cancel, this) | ||||
| { | ||||
|     enum { HEIGHT = 800, WIDTH = 400, SPACING = 5 }; | ||||
| 
 | ||||
|  | @ -95,22 +98,47 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) | |||
|     auto *topsizer = new wxBoxSizer(wxVERTICAL); | ||||
| 
 | ||||
|     job_list = new wxDataViewListCtrl(this, wxID_ANY); | ||||
|     // Note: Keep these in sync with Column
 | ||||
|     job_list->AppendTextColumn("ID", wxDATAVIEW_CELL_INERT); | ||||
|     job_list->AppendProgressColumn("Progress", wxDATAVIEW_CELL_INERT); | ||||
|     job_list->AppendTextColumn("Status", wxDATAVIEW_CELL_INERT); | ||||
|     job_list->AppendTextColumn("Host", wxDATAVIEW_CELL_INERT); | ||||
|     job_list->AppendTextColumn("Filename", wxDATAVIEW_CELL_INERT); | ||||
|     job_list->AppendTextColumn("error_message", wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER, wxDATAVIEW_COL_HIDDEN); | ||||
| 
 | ||||
|     auto *btnsizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|     auto *btn_cancel = new wxButton(this, wxID_DELETE, _(L("Cancel selected")));   // TODO: enable based on status ("show error" for failed jobs)
 | ||||
|     btn_cancel = new wxButton(this, wxID_DELETE, _(L("Cancel selected"))); | ||||
|     btn_cancel->Disable(); | ||||
|     btn_error = new wxButton(this, wxID_ANY, _(L("Show error message"))); | ||||
|     btn_error->Disable(); | ||||
|     auto *btn_close = new wxButton(this, wxID_CANCEL, _(L("Close"))); | ||||
|     btnsizer->Add(btn_cancel, 0, wxRIGHT, SPACING); | ||||
|     btnsizer->Add(btn_error, 0); | ||||
|     btnsizer->AddStretchSpacer(); | ||||
|     btnsizer->Add(btn_close); | ||||
| 
 | ||||
|     topsizer->Add(job_list, 1, wxEXPAND | wxBOTTOM, SPACING); | ||||
|     topsizer->Add(btnsizer, 0, wxEXPAND); | ||||
|     SetSizer(topsizer); | ||||
| 
 | ||||
|     job_list->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxDataViewEvent&) { on_list_select(); }); | ||||
| 
 | ||||
|     btn_cancel->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { | ||||
|         int selected = job_list->GetSelectedRow(); | ||||
|         if (selected == wxNOT_FOUND) { return; } | ||||
| 
 | ||||
|         const JobState state = get_state(selected); | ||||
|         if (state < ST_ERROR) { | ||||
|             // TODO: cancel
 | ||||
|             GUI::wxGetApp().printhost_job_queue().cancel(selected); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     btn_error->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { | ||||
|         int selected = job_list->GetSelectedRow(); | ||||
|         if (selected == wxNOT_FOUND) { return; } | ||||
|         GUI::show_error(nullptr, job_list->GetTextValue(selected, COL_ERRORMSG)); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::append_job(const PrintHostJob &job) | ||||
|  | @ -123,31 +151,83 @@ void PrintHostQueueDialog::append_job(const PrintHostJob &job) | |||
|     fields.push_back(wxVariant(_(L("Enqueued")))); | ||||
|     fields.push_back(wxVariant(job.printhost->get_host())); | ||||
|     fields.push_back(wxVariant(job.upload_data.upload_path.string())); | ||||
|     job_list->AppendItem(fields); | ||||
|     fields.push_back(wxVariant("")); | ||||
|     job_list->AppendItem(fields, static_cast<wxUIntPtr>(ST_NEW)); | ||||
| } | ||||
| 
 | ||||
| PrintHostQueueDialog::JobState PrintHostQueueDialog::get_state(int idx) | ||||
| { | ||||
|     wxCHECK_MSG(idx >= 0 && idx < job_list->GetItemCount(), ST_ERROR, "Out of bounds access to job list"); | ||||
|     return static_cast<JobState>(job_list->GetItemData(job_list->RowToItem(idx))); | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::set_state(int idx, JobState state) | ||||
| { | ||||
|     wxCHECK_RET(idx >= 0 && idx < job_list->GetItemCount(), "Out of bounds access to job list"); | ||||
|     job_list->SetItemData(job_list->RowToItem(idx), static_cast<wxUIntPtr>(state)); | ||||
| 
 | ||||
|     switch (state) { | ||||
|         case ST_NEW:        job_list->SetValue(_(L("Enqueued")), idx, COL_STATUS); break; | ||||
|         case ST_PROGRESS:   job_list->SetValue(_(L("Uploading")), idx, COL_STATUS); break; | ||||
|         case ST_ERROR:      job_list->SetValue(_(L("Error")), idx, COL_STATUS); break; | ||||
|         case ST_CANCELLING: job_list->SetValue(_(L("Cancelling")), idx, COL_STATUS); break; | ||||
|         case ST_CANCELLED:  job_list->SetValue(_(L("Cancelled")), idx, COL_STATUS); break; | ||||
|         case ST_COMPLETED:  job_list->SetValue(_(L("Completed")), idx, COL_STATUS); break; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::on_list_select() | ||||
| { | ||||
|     int selected = job_list->GetSelectedRow(); | ||||
|     if (selected != wxNOT_FOUND) { | ||||
|         const JobState state = get_state(selected); | ||||
|         btn_cancel->Enable(state < ST_ERROR); | ||||
|         btn_error->Enable(state == ST_ERROR); | ||||
|         Layout(); | ||||
|     } else { | ||||
|         btn_cancel->Disable(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::on_progress(Event &evt) | ||||
| { | ||||
|     wxCHECK_RET(evt.job_id < job_list->GetItemCount(), "Out of bounds access to job list"); | ||||
| 
 | ||||
|     const wxVariant status(evt.progress < 100 ? _(L("Uploading")) : _(L("Complete"))); | ||||
|     if (evt.progress < 100) { | ||||
|         set_state(evt.job_id, ST_PROGRESS); | ||||
|         job_list->SetValue(wxVariant(evt.progress), evt.job_id, COL_PROGRESS); | ||||
|     } else { | ||||
|         set_state(evt.job_id, ST_COMPLETED); | ||||
|         job_list->SetValue(wxVariant(100), evt.job_id, COL_PROGRESS); | ||||
|     } | ||||
| 
 | ||||
|     job_list->SetValue(wxVariant(evt.progress), evt.job_id, 1); | ||||
|     job_list->SetValue(status, evt.job_id, 2); | ||||
|     on_list_select(); | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::on_error(Event &evt) | ||||
| { | ||||
|     wxCHECK_RET(evt.job_id < job_list->GetItemCount(), "Out of bounds access to job list"); | ||||
| 
 | ||||
|     job_list->SetValue(wxVariant(0), evt.job_id, 1); | ||||
|     job_list->SetValue(wxVariant(_(L("Error"))), evt.job_id, 2); | ||||
| 
 | ||||
|     // TODO: keep the error for repeated display
 | ||||
|     set_state(evt.job_id, ST_ERROR); | ||||
| 
 | ||||
|     auto errormsg = wxString::Format("%s\n%s", _(L("Error uploading to print host:")), evt.error); | ||||
|     job_list->SetValue(wxVariant(0), evt.job_id, COL_PROGRESS); | ||||
|     job_list->SetValue(wxVariant(errormsg), evt.job_id, COL_ERRORMSG);    // Stashes the error message into a hidden column for later
 | ||||
| 
 | ||||
|     on_list_select(); | ||||
| 
 | ||||
|     GUI::show_error(nullptr, std::move(errormsg)); | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::on_cancel(Event &evt) | ||||
| { | ||||
|     wxCHECK_RET(evt.job_id < job_list->GetItemCount(), "Out of bounds access to job list"); | ||||
| 
 | ||||
|     set_state(evt.job_id, ST_CANCELLED); | ||||
|     job_list->SetValue(wxVariant(0), evt.job_id, COL_PROGRESS); | ||||
| 
 | ||||
|     on_list_select(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| }} | ||||
|  |  | |||
|  | @ -13,10 +13,12 @@ | |||
| #include "MsgDialog.hpp" | ||||
| #include "../Utils/PrintHost.hpp" | ||||
| 
 | ||||
| class wxButton; | ||||
| class wxTextCtrl; | ||||
| class wxCheckBox; | ||||
| class wxDataViewListCtrl; | ||||
| 
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| struct PrintHostJob; | ||||
|  | @ -60,17 +62,43 @@ public: | |||
| 
 | ||||
|     void append_job(const PrintHostJob &job); | ||||
| private: | ||||
|     enum Column { | ||||
|         COL_ID, | ||||
|         COL_PROGRESS, | ||||
|         COL_STATUS, | ||||
|         COL_HOST, | ||||
|         COL_FILENAME, | ||||
|         COL_ERRORMSG, | ||||
|     }; | ||||
| 
 | ||||
|     enum JobState { | ||||
|         ST_NEW, | ||||
|         ST_PROGRESS, | ||||
|         ST_ERROR, | ||||
|         ST_CANCELLING, | ||||
|         ST_CANCELLED, | ||||
|         ST_COMPLETED, | ||||
|     }; | ||||
| 
 | ||||
|     wxButton *btn_cancel; | ||||
|     wxButton *btn_error; | ||||
|     wxDataViewListCtrl *job_list; | ||||
|     // Note: EventGuard prevents delivery of progress evts to a freed PrintHostQueueDialog
 | ||||
|     EventGuard on_progress_evt; | ||||
|     EventGuard on_error_evt; | ||||
|     EventGuard on_cancel_evt; | ||||
| 
 | ||||
|     JobState get_state(int idx); | ||||
|     void set_state(int idx, JobState); | ||||
|     void on_list_select(); | ||||
|     void on_progress(Event&); | ||||
|     void on_error(Event&); | ||||
|     void on_cancel(Event&); | ||||
| }; | ||||
| 
 | ||||
| wxDECLARE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event); | ||||
| wxDECLARE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event); | ||||
| wxDECLARE_EVENT(EVT_PRINTHOST_CANCEL, PrintHostQueueDialog::Event); | ||||
| 
 | ||||
| 
 | ||||
| }} | ||||
|  |  | |||
|  | @ -116,7 +116,7 @@ SysInfoDialog::SysInfoDialog() | |||
|     buttons->Insert(0, btn_copy_to_clipboard, 0, wxLEFT, 5); | ||||
|     btn_copy_to_clipboard->Bind(wxEVT_BUTTON, &SysInfoDialog::onCopyToClipboard, this); | ||||
| 
 | ||||
|     this->SetEscapeId(wxID_CLOSE); | ||||
|     this->SetEscapeId(wxID_OK); | ||||
|     this->Bind(wxEVT_BUTTON, &SysInfoDialog::onCloseDialog, this, wxID_OK); | ||||
|     main_sizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3); | ||||
|      | ||||
|  |  | |||
|  | @ -54,91 +54,46 @@ wxString Duet::get_test_failed_msg (wxString &msg) const | |||
| 	return wxString::Format("%s: %s", _(L("Could not connect to Duet")), msg); | ||||
| } | ||||
| 
 | ||||
| // bool Duet::send_gcode(const std::string &filename) const
 | ||||
| // {
 | ||||
| // 	enum { PROGRESS_RANGE = 1000 };
 | ||||
| 
 | ||||
| // 	const auto errortitle = _(L("Error while uploading to the Duet"));
 | ||||
| // 	fs::path filepath(filename);
 | ||||
| 
 | ||||
| // 	GUI::PrintHostSendDialog send_dialog(filepath.filename());
 | ||||
| // 	if (send_dialog.ShowModal() != wxID_OK) { return false; }
 | ||||
| 
 | ||||
| // 	const bool print = send_dialog.start_print();
 | ||||
| // 	const auto upload_filepath = send_dialog.filename();
 | ||||
| // 	const auto upload_filename = upload_filepath.filename();
 | ||||
| // 	const auto upload_parent_path = upload_filepath.parent_path();
 | ||||
| 
 | ||||
| // 	wxProgressDialog progress_dialog(
 | ||||
| // 	 	_(L("Duet upload")),
 | ||||
| // 	 	_(L("Sending G-code file to Duet...")),
 | ||||
| // 		PROGRESS_RANGE, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
 | ||||
| // 	progress_dialog.Pulse();
 | ||||
| 
 | ||||
| // 	wxString connect_msg;
 | ||||
| // 	if (!connect(connect_msg)) {
 | ||||
| // 		auto errormsg = wxString::Format("%s: %s", errortitle, connect_msg);
 | ||||
| // 		GUI::show_error(&progress_dialog, std::move(errormsg));
 | ||||
| // 		return false;
 | ||||
| // 	}
 | ||||
| 
 | ||||
| // 	bool res = true;
 | ||||
| 
 | ||||
| // 	auto upload_cmd = get_upload_url(upload_filepath.string());
 | ||||
| // 	BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filename: %2%, path: %3%, print: %4%, command: %5%")
 | ||||
| // 		% filepath.string()
 | ||||
| // 		% upload_filename.string()
 | ||||
| // 		% upload_parent_path.string()
 | ||||
| // 		% print
 | ||||
| // 		% upload_cmd;
 | ||||
| 
 | ||||
| // 	auto http = Http::post(std::move(upload_cmd));
 | ||||
| // 	http.set_post_body(filename)
 | ||||
| // 		.on_complete([&](std::string body, unsigned status) {
 | ||||
| // 			BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: File uploaded: HTTP %1%: %2%") % status % body;
 | ||||
| // 			progress_dialog.Update(PROGRESS_RANGE);
 | ||||
| 
 | ||||
| // 			int err_code = get_err_code_from_body(body);
 | ||||
| // 			if (err_code != 0) {
 | ||||
| // 				auto msg = format_error(body, L("Unknown error occured"), 0);
 | ||||
| // 				GUI::show_error(&progress_dialog, std::move(msg));
 | ||||
| // 				res = false;
 | ||||
| // 			} else if (print) {
 | ||||
| // 				wxString errormsg;
 | ||||
| // 				res = start_print(errormsg, upload_filepath.string());
 | ||||
| // 				if (!res) {
 | ||||
| // 					GUI::show_error(&progress_dialog, std::move(errormsg));
 | ||||
| // 				}
 | ||||
| // 			}
 | ||||
| // 		})
 | ||||
| // 		.on_error([&](std::string body, std::string error, unsigned status) {
 | ||||
| // 			BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body;
 | ||||
| // 			auto errormsg = wxString::Format("%s: %s", errortitle, format_error(body, error, status));
 | ||||
| // 			GUI::show_error(&progress_dialog, std::move(errormsg));
 | ||||
| // 			res = false;
 | ||||
| // 		})
 | ||||
| // 		.on_progress([&](Http::Progress progress, bool &cancel) {
 | ||||
| // 			if (cancel) {
 | ||||
| // 				// Upload was canceled
 | ||||
| // 				res = false;
 | ||||
| // 			} else if (progress.ultotal > 0) {
 | ||||
| // 				int value = PROGRESS_RANGE * progress.ulnow / progress.ultotal;
 | ||||
| // 				cancel = !progress_dialog.Update(std::min(value, PROGRESS_RANGE - 1));    // Cap the value to prevent premature dialog closing
 | ||||
| // 			} else {
 | ||||
| // 				cancel = !progress_dialog.Pulse();
 | ||||
| // 			}
 | ||||
| // 		})
 | ||||
| // 		.perform_sync();
 | ||||
| 
 | ||||
| // 	disconnect();
 | ||||
| 
 | ||||
| // 	return res;
 | ||||
| // }
 | ||||
| 
 | ||||
| bool Duet::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const | ||||
| { | ||||
| 	// XXX: TODO
 | ||||
| 	throw "unimplemented"; | ||||
| 	wxString connect_msg; | ||||
| 	if (!connect(connect_msg)) { | ||||
| 		error_fn(std::move(connect_msg)); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	bool res = true; | ||||
| 
 | ||||
| 	auto upload_cmd = get_upload_url(upload_data.upload_path.string()); | ||||
| 	BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filepath: %2%, print: %3%, command: %4%") | ||||
| 		% upload_data.source_path | ||||
| 		% upload_data.upload_path | ||||
| 		% upload_data.start_print | ||||
| 		% upload_cmd; | ||||
| 
 | ||||
| 	auto http = Http::post(std::move(upload_cmd)); | ||||
| 	http.set_post_body(upload_data.source_path) | ||||
| 		.on_complete([&](std::string body, unsigned status) { | ||||
| 			BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: File uploaded: HTTP %1%: %2%") % status % body; | ||||
| 		}) | ||||
| 		.on_error([&](std::string body, std::string error, unsigned status) { | ||||
| 			BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body; | ||||
| 			error_fn(format_error(body, error, status)); | ||||
| 			res = false; | ||||
| 		}) | ||||
| 		.on_progress([&](Http::Progress progress, bool &cancel) { | ||||
| 			prorgess_fn(std::move(progress), cancel); | ||||
| 			if (cancel) { | ||||
| 				// Upload was canceled
 | ||||
| 				BOOST_LOG_TRIVIAL(info) << "Duet: Upload canceled"; | ||||
| 				res = false; | ||||
| 			} | ||||
| 		}) | ||||
| 		.perform_sync(); | ||||
| 
 | ||||
| 	disconnect(); | ||||
| 
 | ||||
| 	return res; | ||||
| } | ||||
| 
 | ||||
| bool Duet::has_auto_discovery() const | ||||
|  | @ -241,20 +196,10 @@ std::string Duet::timestamp_str() const | |||
| 	return std::string(buffer); | ||||
| } | ||||
| 
 | ||||
| wxString Duet::format_error(const std::string &body, const std::string &error, unsigned status) | ||||
| { | ||||
| 	if (status != 0) { | ||||
| 		auto wxbody = wxString::FromUTF8(body.data()); | ||||
| 		return wxString::Format("HTTP %u: %s", status, wxbody); | ||||
| 	} else { | ||||
| 		return wxString::FromUTF8(error.data()); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| bool Duet::start_print(wxString &msg, const std::string &filename) const  | ||||
| { | ||||
| 	bool res = false; | ||||
| 	 | ||||
| 
 | ||||
| 	auto url = (boost::format("%1%rr_gcode?gcode=M32%%20\"%2%\"") | ||||
| 			% get_base_url() | ||||
| 			% Http::url_encode(filename)).str(); | ||||
|  |  | |||
|  | @ -26,6 +26,7 @@ public: | |||
| 	virtual bool has_auto_discovery() const; | ||||
| 	virtual bool can_test() const; | ||||
| 	virtual std::string get_host() const { return host; } | ||||
| 
 | ||||
| private: | ||||
| 	std::string host; | ||||
| 	std::string password; | ||||
|  | @ -38,7 +39,6 @@ private: | |||
| 	void disconnect() const; | ||||
| 	bool start_print(wxString &msg, const std::string &filename) const; | ||||
| 	int get_err_code_from_body(const std::string &body) const; | ||||
| 	static wxString format_error(const std::string &body, const std::string &error, unsigned status); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -149,7 +149,9 @@ int Http::priv::xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_o | |||
| 		self->progressfn(progress, cb_cancel); | ||||
| 	} | ||||
| 
 | ||||
| 	return self->cancel || cb_cancel; | ||||
| 	if (cb_cancel) { self->cancel = true; } | ||||
| 
 | ||||
| 	return self->cancel; | ||||
| } | ||||
| 
 | ||||
| int Http::priv::xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow) | ||||
|  | @ -280,7 +282,7 @@ void Http::priv::http_perform() | |||
| 	} else { | ||||
| 		long http_status = 0; | ||||
| 		::curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status); | ||||
| 		 | ||||
| 
 | ||||
| 		if (http_status >= 400) { | ||||
| 			if (errorfn) { errorfn(std::move(buffer), std::string(), http_status); } | ||||
| 		} else { | ||||
|  |  | |||
|  | @ -102,7 +102,7 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro | |||
|     auto url = make_url("api/files/local"); | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Uploading file %1% at %2%, filename: %3%, path: %4%, print: %5%") | ||||
|         % upload_data.source_path.string() | ||||
|         % upload_data.source_path | ||||
|         % url | ||||
|         % upload_filename.string() | ||||
|         % upload_parent_path.string() | ||||
|  | @ -118,7 +118,6 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro | |||
|         }) | ||||
|         .on_error([&](std::string body, std::string error, unsigned status) { | ||||
|             BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body; | ||||
|             // error_fn(std::move(body), std::move(error), status);
 | ||||
|             error_fn(format_error(body, error, status)); | ||||
|             res = false; | ||||
|         }) | ||||
|  | @ -126,7 +125,7 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro | |||
|             prorgess_fn(std::move(progress), cancel); | ||||
|             if (cancel) { | ||||
|                 // Upload was canceled
 | ||||
|                 BOOST_LOG_TRIVIAL(error) << "Octoprint: Upload canceled"; | ||||
|                 BOOST_LOG_TRIVIAL(info) << "Octoprint: Upload canceled"; | ||||
|                 res = false; | ||||
|             } | ||||
|         }) | ||||
|  | @ -172,16 +171,6 @@ std::string OctoPrint::make_url(const std::string &path) const | |||
|     } | ||||
| } | ||||
| 
 | ||||
| wxString OctoPrint::format_error(const std::string &body, const std::string &error, unsigned status) | ||||
| { | ||||
|     if (status != 0) { | ||||
|         auto wxbody = wxString::FromUTF8(body.data()); | ||||
|         return wxString::Format("HTTP %u: %s", status, wxbody); | ||||
|     } else { | ||||
|         return wxString::FromUTF8(error.data()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // SLAHost
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -38,7 +38,6 @@ private: | |||
| 
 | ||||
|     void set_auth(Http &http) const; | ||||
|     std::string make_url(const std::string &path) const; | ||||
|     static wxString format_error(const std::string &body, const std::string &error, unsigned status); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -38,6 +38,16 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config) | |||
|     } | ||||
| } | ||||
| 
 | ||||
| wxString PrintHost::format_error(const std::string &body, const std::string &error, unsigned status) const | ||||
| { | ||||
|     if (status != 0) { | ||||
|         auto wxbody = wxString::FromUTF8(body.data()); | ||||
|         return wxString::Format("HTTP %u: %s", status, wxbody); | ||||
|     } else { | ||||
|         return wxString::FromUTF8(error.data()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| struct PrintHostJobQueue::priv | ||||
| { | ||||
|  | @ -49,6 +59,7 @@ struct PrintHostJobQueue::priv | |||
|     Channel<size_t> channel_cancels; | ||||
|     size_t job_id = 0; | ||||
|     int prev_progress = -1; | ||||
|     fs::path source_to_remove; | ||||
| 
 | ||||
|     std::thread bg_thread; | ||||
|     bool bg_exit = false; | ||||
|  | @ -57,9 +68,14 @@ struct PrintHostJobQueue::priv | |||
| 
 | ||||
|     priv(PrintHostJobQueue *q) : q(q) {} | ||||
| 
 | ||||
|     void emit_progress(int progress); | ||||
|     void emit_error(wxString error); | ||||
|     void emit_cancel(size_t id); | ||||
|     void start_bg_thread(); | ||||
|     void bg_thread_main(); | ||||
|     void progress_fn(Http::Progress progress, bool &cancel); | ||||
|     void remove_source(const fs::path &path); | ||||
|     void remove_source(); | ||||
|     void perform_job(PrintHostJob the_job); | ||||
| }; | ||||
| 
 | ||||
|  | @ -78,6 +94,24 @@ PrintHostJobQueue::~PrintHostJobQueue() | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::emit_progress(int progress) | ||||
| { | ||||
|     auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_PROGRESS, queue_dialog->GetId(), job_id, progress); | ||||
|     wxQueueEvent(queue_dialog, evt); | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::emit_error(wxString error) | ||||
| { | ||||
|     auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_ERROR, queue_dialog->GetId(), job_id, std::move(error)); | ||||
|     wxQueueEvent(queue_dialog, evt); | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::emit_cancel(size_t id) | ||||
| { | ||||
|     auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_CANCEL, queue_dialog->GetId(), id); | ||||
|     wxQueueEvent(queue_dialog, evt); | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::start_bg_thread() | ||||
| { | ||||
|     if (bg_thread.joinable()) { return; } | ||||
|  | @ -96,21 +130,43 @@ void PrintHostJobQueue::priv::bg_thread_main() | |||
|         // Pick up jobs from the job channel:
 | ||||
|         while (! bg_exit) { | ||||
|             auto job = channel_jobs.pop();   // Sleeps in a cond var if there are no jobs
 | ||||
|             source_to_remove = job.upload_data.source_path; | ||||
| 
 | ||||
|             BOOST_LOG_TRIVIAL(debug) << boost::format("PrintHostJobQueue/bg_thread: Received job: [%1%]: `%2%` -> `%3%`, cancelled: %4%") | ||||
|                 % job_id | ||||
|                 % job.upload_data.upload_path | ||||
|                 % job.printhost->get_host() | ||||
|                 % job.cancelled; | ||||
| 
 | ||||
|             if (! job.cancelled) { | ||||
|                 perform_job(std::move(job)); | ||||
|             } | ||||
| 
 | ||||
|             remove_source(); | ||||
|             job_id++; | ||||
|         } | ||||
|     } catch (const std::exception &e) { | ||||
|         auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_ERROR, queue_dialog->GetId(), job_id, e.what()); | ||||
|         wxQueueEvent(queue_dialog, evt); | ||||
|         emit_error(e.what()); | ||||
|     } catch (...) { | ||||
|         wxTheApp->OnUnhandledException(); | ||||
|         emit_error("Unknown exception"); | ||||
|     } | ||||
| 
 | ||||
|     // Cleanup leftover files, if any
 | ||||
|     remove_source(); | ||||
|     auto jobs = channel_jobs.lock_rw(); | ||||
|     for (const PrintHostJob &job : *jobs) { | ||||
|         remove_source(job.upload_data.source_path); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::progress_fn(Http::Progress progress, bool &cancel) | ||||
| { | ||||
|     if (cancel) { | ||||
|         // When cancel is true from the start, Http indicates request has been cancelled
 | ||||
|         emit_cancel(job_id); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (bg_exit) { | ||||
|         cancel = true; | ||||
|         return; | ||||
|  | @ -125,48 +181,59 @@ void PrintHostJobQueue::priv::progress_fn(Http::Progress progress, bool &cancel) | |||
|             if (cancel_id == job_id) { | ||||
|                 cancel = true; | ||||
|             } else if (cancel_id > job_id) { | ||||
|                 jobs->at(cancel_id - job_id).cancelled = true; | ||||
|                 const size_t idx = cancel_id - job_id - 1; | ||||
|                 if (idx < jobs->size()) { | ||||
|                     jobs->at(idx).cancelled = true; | ||||
|                     BOOST_LOG_TRIVIAL(debug) << boost::format("PrintHostJobQueue: Job id %1% cancelled") % cancel_id; | ||||
|                     emit_cancel(cancel_id); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         cancels->clear(); | ||||
|     } | ||||
| 
 | ||||
|     int gui_progress = progress.ultotal > 0 ? 100*progress.ulnow / progress.ultotal : 0; | ||||
|     if (gui_progress != prev_progress) { | ||||
|         auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_PROGRESS, queue_dialog->GetId(), job_id, gui_progress); | ||||
|         wxQueueEvent(queue_dialog, evt); | ||||
|         prev_progress = gui_progress; | ||||
|     if (! cancel) { | ||||
|         int gui_progress = progress.ultotal > 0 ? 100*progress.ulnow / progress.ultotal : 0; | ||||
|         if (gui_progress != prev_progress) { | ||||
|             emit_progress(gui_progress); | ||||
|             prev_progress = gui_progress; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::remove_source(const fs::path &path) | ||||
| { | ||||
|     if (! path.empty()) { | ||||
|         boost::system::error_code ec; | ||||
|         fs::remove(path, ec); | ||||
|         if (ec) { | ||||
|             BOOST_LOG_TRIVIAL(error) << boost::format("PrintHostJobQueue: Error removing file `%1%`: %2%") % path % ec; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::remove_source() | ||||
| { | ||||
|     remove_source(source_to_remove); | ||||
|     source_to_remove.clear(); | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job) | ||||
| { | ||||
|     if (bg_exit || the_job.empty()) { return; } | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(debug) << boost::format("PrintHostJobQueue/bg_thread: Got job: `%1%` -> `%2%`") | ||||
|         % the_job.upload_data.upload_path | ||||
|         % the_job.printhost->get_host(); | ||||
| 
 | ||||
|     const fs::path gcode_path = the_job.upload_data.source_path; | ||||
|     emit_progress(0);   // Indicate the upload is starting
 | ||||
| 
 | ||||
|     bool success = the_job.printhost->upload(std::move(the_job.upload_data), | ||||
|         [this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); }, | ||||
|         [this](wxString error) { | ||||
|             auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_ERROR, queue_dialog->GetId(), job_id, std::move(error)); | ||||
|             wxQueueEvent(queue_dialog, evt); | ||||
|             emit_error(std::move(error)); | ||||
|         } | ||||
|     ); | ||||
| 
 | ||||
|     if (success) { | ||||
|         auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_PROGRESS, queue_dialog->GetId(), job_id, 100); | ||||
|         wxQueueEvent(queue_dialog, evt); | ||||
|     } | ||||
| 
 | ||||
|     boost::system::error_code ec; | ||||
|     fs::remove(gcode_path, ec); | ||||
|     if (ec) { | ||||
|         BOOST_LOG_TRIVIAL(error) << boost::format("PrintHostJobQueue: Error removing file `%1%`: %2%") % gcode_path % ec; | ||||
|         emit_progress(100); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -177,5 +244,10 @@ void PrintHostJobQueue::enqueue(PrintHostJob job) | |||
|     p->channel_jobs.push(std::move(job)); | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::cancel(size_t id) | ||||
| { | ||||
|     p->channel_cancels.push(id); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -41,6 +41,9 @@ public: | |||
|     virtual std::string get_host() const = 0; | ||||
| 
 | ||||
|     static PrintHost* get_print_host(DynamicPrintConfig *config); | ||||
| 
 | ||||
| protected: | ||||
|     virtual wxString format_error(const std::string &body, const std::string &error, unsigned status) const; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  | @ -55,6 +58,7 @@ struct PrintHostJob | |||
|     PrintHostJob(PrintHostJob &&other) | ||||
|         : upload_data(std::move(other.upload_data)) | ||||
|         , printhost(std::move(other.printhost)) | ||||
|         , cancelled(other.cancelled) | ||||
|     {} | ||||
| 
 | ||||
|     PrintHostJob(DynamicPrintConfig *config) | ||||
|  | @ -66,6 +70,7 @@ struct PrintHostJob | |||
|     { | ||||
|         upload_data = std::move(other.upload_data); | ||||
|         printhost = std::move(other.printhost); | ||||
|         cancelled = other.cancelled; | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 bubnikv
						bubnikv