diff --git a/resources/icons/Slic3r_32px.png b/resources/icons/Slic3r_32px.png new file mode 100644 index 000000000..6bf5a9cd1 Binary files /dev/null and b/resources/icons/Slic3r_32px.png differ diff --git a/resources/icons/overlay/sla_support_points_hover.png b/resources/icons/overlay/sla_support_points_hover.png index 6303232b2..55955f3d6 100644 Binary files a/resources/icons/overlay/sla_support_points_hover.png and b/resources/icons/overlay/sla_support_points_hover.png differ diff --git a/resources/icons/overlay/sla_support_points_off.png b/resources/icons/overlay/sla_support_points_off.png index b654366d5..7bf84a3e6 100644 Binary files a/resources/icons/overlay/sla_support_points_off.png and b/resources/icons/overlay/sla_support_points_off.png differ diff --git a/resources/icons/overlay/sla_support_points_on.png b/resources/icons/overlay/sla_support_points_on.png index 8c1e69565..879b9b241 100644 Binary files a/resources/icons/overlay/sla_support_points_on.png and b/resources/icons/overlay/sla_support_points_on.png differ diff --git a/resources/icons/toolbar_background.png b/resources/icons/toolbar_background.png index 2b5ea013b..e7ce3c146 100644 Binary files a/resources/icons/toolbar_background.png and b/resources/icons/toolbar_background.png differ diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index e440b35df..49d8ac1a5 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,6 +1,21 @@ min_slic3r_version = 1.42.0-alpha +0.4.0-alpha3 Update of SLA profiles 0.4.0-alpha2 First SLA profiles +min_slic3r_version = 1.41.1 +0.3.3 Prusament PETG released +0.3.2 New MK2.5 and MK3 FW versions +0.3.1 New MK2.5 and MK3 FW versions +0.3.0 New MK2.5 and MK3 FW version min_slic3r_version = 1.41.0-alpha +0.2.9 New MK2.5 and MK3 FW versions +0.2.8 New MK2.5 and MK3 FW version +min_slic3r_version = 1.41.1 +0.2.7 New MK2.5 and MK3 FW version +0.2.6 Added MMU2 MK2.5 settings +min_slic3r_version = 1.41.0-alpha +0.2.5 Prusament is out - added prusament settings +0.2.4 Added soluble support profiles for MMU2 +0.2.3 Added materials for MMU2 single mode, edited MK3 xy stealth feedrate limit 0.2.2 Edited MMU2 Single mode purge line 0.2.1 Added PET and BVOH settings for MMU2 0.2.0-beta5 Fixed MMU1 ramming parameters diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index d5425dc12..7bdcb6906 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.4.0-alpha2 +config_version = 0.4.0-alpha3 # Where to get the updates from? config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch/ @@ -41,7 +41,7 @@ variants = 0.4 [printer_model:SL1] name = Original Prusa SL1 -variants = default; dummy +variants = default # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. @@ -1137,19 +1137,132 @@ min_fan_speed = 100 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 220 -[sla_material:*common*] +[sla_print:*common*] +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_SL1.*/ layer_height = 0.05 -initial_layer_height = 0.3 +output_filename_format = [input_filename_base].dwz +pad_edge_radius = 0.5 +pad_enable = 1 +pad_max_merge_distance = 50 +pad_wall_height = 3 +pad_wall_thickness = 1 +support_base_diameter = 3 +support_base_height = 0.5 +support_critical_angle = 45 +support_density_at_45 = 250 +support_density_at_horizontal = 500 +support_head_front_diameter = 0.4 +support_head_penetration = 0.4 +support_head_width = 3 +support_max_bridge_length = 10 +support_minimal_z = 0 +support_object_elevation = 5 +support_pillar_diameter = 1 +support_pillar_widening_factor = 0 +supports_enable = 1 + +[sla_print:0.025 UltraDetail] +inherits = *common* +layer_height = 0.025 +support_head_width = 2 + +[sla_print:0.035 Detail] +inherits = *common* +layer_height = 0.035 + +[sla_print:0.05 Normal] +inherits = *common* +layer_height = 0.05 + +[sla_print:0.1 Fast] +inherits = *common* +layer_height = 0.1 + +########### Materials 0.025 + +[sla_material:*common 0.05*] +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_SL1.*/ +compatible_prints_condition = layer_height == 0.05 +exposure_time = 12 +initial_exposure_time = 45 +initial_layer_height = 0.5 +material_correction_curing = 1,1,1 +material_correction_printing = 1,1,1 +material_notes = + +[sla_material:*common 0.025*] +inherits = *common 0.05* +compatible_prints_condition = layer_height == 0.025 exposure_time = 10 -initial_exposure_time = 15 -material_correction_printing = 1, 1, 1 -material_correction_curing = 1, 1, 1 +initial_exposure_time = 35 -[sla_material:Material 1] -inherits = *common* +[sla_material:*common 0.035*] +inherits = *common 0.05* +compatible_prints_condition = layer_height == 0.035 +exposure_time = 13 +initial_exposure_time = 40 -[sla_material:Material 2] -inherits = *common* +[sla_material:*common 0.1*] +inherits = *common 0.05* +compatible_prints_condition = layer_height == 0.1 +exposure_time = 20 +initial_exposure_time = 90 + +########### Materials 0.025 + +[sla_material:Jamg He Transparent Clear 0.025] +inherits = *common 0.025* + +[sla_material:Jamg He Transparent Green 0.025] +inherits = *common 0.025* + +[sla_material:Jamg He Transparent Orange 0.025] +inherits = *common 0.025* + +[sla_material:Jamg He Transparent Red 0.025] +inherits = *common 0.025* + +########### Materials 0.05 + +[sla_material:Jamg He Transparent Clear 0.05] +inherits = *common 0.05* + +[sla_material:Jamg He Transparent Green 0.05] +inherits = *common 0.05* + +[sla_material:Jamg He Transparent Orange 0.05] +inherits = *common 0.05* + +[sla_material:Jamg He Transparent Red 0.05] +inherits = *common 0.05* + +########### Materials 0.035 + +[sla_material:Jamg He Transparent Clear 0.035] +inherits = *common 0.035* + +[sla_material:Jamg He Transparent Green 0.035] +inherits = *common 0.035* + +[sla_material:Jamg He Transparent Orange 0.035] +inherits = *common 0.035* + +[sla_material:Jamg He Transparent Red 0.035] +inherits = *common 0.035* + +########### Materials 0.1 + +[sla_material:Jamg He Transparent Clear 0.1] +inherits = *common 0.1* + +[sla_material:Jamg He Transparent Green 0.1] +inherits = *common 0.1* + +[sla_material:Jamg He Transparent Orange 0.1] +inherits = *common 0.1* + +[sla_material:Jamg He Transparent Red 0.1] +inherits = *common 0.1* [printer:*common*] printer_technology = FFF @@ -1467,21 +1580,20 @@ end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG printer_technology = SLA printer_model = SL1 printer_variant = default -default_sla_material_profile = Material 1 -bed_shape = 0x0,150x0,150x100,0x100 -max_print_height = 100 -display_width = 150 -display_height = 100 -display_pixels_x = 2000 -display_pixels_y = 1000 +default_sla_material_profile = Jamg He Transparent Green 0.05 +default_sla_print_profile = 0.05 Normal +bed_shape = 0.98x1.02,119.98x1.02,119.98x68.02,0.98x68.02 +display_height = 68.04 +display_orientation = portrait +display_pixels_x = 2560 +display_pixels_y = 1440 +display_width = 120.96 +max_print_height = 150 printer_correction = 1,1,1 - -[printer:Original Prusa SL1 dummy] -inherits = Original Prusa SL1 -printer_variant = dummy -default_sla_material_profile = Material 2 +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_SL1\n # The obsolete presets will be removed when upgrading from the legacy configuration structure (up to Slic3r 1.39.2) to 1.40.0 and newer. [obsolete_presets] print="0.05mm DETAIL 0.25 nozzle";"0.05mm DETAIL MK3";"0.05mm DETAIL";"0.20mm NORMAL MK3";"0.35mm FAST MK3";"print:0.15mm OPTIMAL MK3 MMU2";"print:0.20mm FAST MK3 MMU2" filament="ColorFabb Brass Bronze 1.75mm";"ColorFabb HT 1.75mm";"ColorFabb nGen 1.75mm";"ColorFabb Woodfil 1.75mm";"ColorFabb XT 1.75mm";"ColorFabb XT-CF20 1.75mm";"E3D PC-ABS 1.75mm";"Fillamentum ABS 1.75mm";"Fillamentum ASA 1.75mm";"Generic ABS 1.75mm";"Generic PET 1.75mm";"Generic PLA 1.75mm";"Prusa ABS 1.75mm";"Prusa HIPS 1.75mm";"Prusa PET 1.75mm";"Prusa PLA 1.75mm";"Taulman Bridge 1.75mm";"Taulman T-Glase 1.75mm" +printer="Original Prusa SL1 dummy" diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index 14ed3d22c..28659c512 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -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) { diff --git a/src/libslic3r/Channel.hpp b/src/libslic3r/Channel.hpp index 9cf025f2c..68369af63 100644 --- a/src/libslic3r/Channel.hpp +++ b/src/libslic3r/Channel.hpp @@ -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 diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index d182f1501..562527290 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -135,11 +135,6 @@ objfunc(const PointImpl& bincenter, const ItemGroup& remaining ) { - using Coord = TCoord; - - 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 progressind, std::function stopcondition) { - using ArrangeResult = _IndexedPackGroup; - bool ret = true; // Get the 2D projected shapes with their 3D model instance pointers diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 37b0c0ffc..c599cd83e 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -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, diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index 62e790611..e19f263b6 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -164,6 +164,8 @@ public: /// Get the pad geometry const TriangleMesh& get_pad() const; + void remove_pad(); + }; } diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp index 5d40bb514..6d4a770aa 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -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 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 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 diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 3dc854dc6..eb7c5f0d2 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -592,9 +592,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(); @@ -618,6 +622,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(); @@ -896,6 +902,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); } @@ -921,6 +928,7 @@ void SLAPrint::process() { report_status(*this, int(st), PRINT_STEP_LABELS[currentstep]); print_program[currentstep](); + throw_if_canceled(); set_done(currentstep); } diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 797272223..5d49c3675 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -43,7 +43,7 @@ // Renders a small sphere in the center of the bounding box of the current selection when no gizmo is active #define ENABLE_RENDER_SELECTION_CENTER (0 && ENABLE_1_42_0) // Show visual hints in the 3D scene when sidebar matrix fields have focus -#define ENABLE_SIDEBAR_VISUAL_HINTS (0 && ENABLE_1_42_0) +#define ENABLE_SIDEBAR_VISUAL_HINTS (1 && ENABLE_1_42_0) #endif // _technologies_h_ diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 012203450..961d4822a 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1872,6 +1872,7 @@ GUI::GLCanvas3DManager _3DScene::s_canvas_mgr; GLModel::GLModel() : m_useVBOs(false) { + m_volume.shader_outside_printer_detection_enabled = false; } GLModel::~GLModel() @@ -1879,11 +1880,36 @@ GLModel::~GLModel() m_volume.release_geometry(); } -void GLModel::set_color(float* color, unsigned int size) +void GLModel::set_color(const float* color, unsigned int size) { m_volume.set_render_color(color, size); } +const Vec3d& GLModel::get_offset() const +{ + return m_volume.get_volume_offset(); +} + +void GLModel::set_offset(const Vec3d& offset) +{ + m_volume.set_volume_offset(offset); +} + +const Vec3d& GLModel::get_rotation() const +{ + return m_volume.get_volume_rotation(); +} + +void GLModel::set_rotation(const Vec3d& rotation) +{ + m_volume.set_volume_rotation(rotation); +} + +const Vec3d& GLModel::get_scale() const +{ + return m_volume.get_volume_scaling_factor(); +} + void GLModel::set_scale(const Vec3d& scale) { m_volume.set_volume_scaling_factor(scale); @@ -1894,8 +1920,7 @@ void GLModel::render() const if (m_useVBOs) render_VBOs(); else - { - } + render_legacy(); } void GLModel::render_VBOs() const @@ -1910,8 +1935,9 @@ void GLModel::render_VBOs() const GLint current_program_id; ::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id); GLint color_id = (current_program_id > 0) ? glGetUniformLocation(current_program_id, "uniform_color") : -1; + GLint print_box_detection_id = (current_program_id > 0) ? glGetUniformLocation(current_program_id, "print_box.volume_detection") : -1; - m_volume.render_VBOs(color_id, -1, -1); + m_volume.render_VBOs(color_id, print_box_detection_id, -1); ::glBindBuffer(GL_ARRAY_BUFFER, 0); ::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); @@ -1922,9 +1948,23 @@ void GLModel::render_VBOs() const ::glDisable(GL_BLEND); } -GLArrow::GLArrow() - : GLModel() +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) @@ -1932,7 +1972,7 @@ bool GLArrow::on_init(bool useVBOs) Pointf3s vertices; std::vector triangles; - // top face + // bottom face vertices.emplace_back(0.5, 0.0, -0.1); vertices.emplace_back(0.5, 2.0, -0.1); vertices.emplace_back(1.0, 2.0, -0.1); @@ -1941,7 +1981,7 @@ bool GLArrow::on_init(bool useVBOs) vertices.emplace_back(-0.5, 2.0, -0.1); vertices.emplace_back(-0.5, 0.0, -0.1); - // bottom face + // top face vertices.emplace_back(0.5, 0.0, 0.1); vertices.emplace_back(0.5, 2.0, 0.1); vertices.emplace_back(1.0, 2.0, 0.1); @@ -1990,6 +2030,126 @@ bool GLArrow::on_init(bool useVBOs) m_volume.finalize_geometry(m_useVBOs); return true; } + +GLCurvedArrow::GLCurvedArrow(unsigned int resolution) + : GLModel() + , m_resolution(resolution) +{ + if (m_resolution == 0) + m_resolution = 1; +} + +bool GLCurvedArrow::on_init(bool useVBOs) +{ + Pointf3s vertices; + std::vector triangles; + + double ext_radius = 2.5; + double int_radius = 1.5; + double step = 0.5 * (double)PI / (double)m_resolution; + + unsigned int vertices_per_level = 4 + 2 * m_resolution; + + // bottom face + vertices.emplace_back(0.0, 1.5, -0.1); + vertices.emplace_back(0.0, 1.0, -0.1); + vertices.emplace_back(-1.0, 2.0, -0.1); + vertices.emplace_back(0.0, 3.0, -0.1); + vertices.emplace_back(0.0, 2.5, -0.1); + + for (unsigned int i = 1; i <= m_resolution; ++i) + { + double angle = (double)i * step; + double x = ext_radius * ::sin(angle); + double y = ext_radius * ::cos(angle); + + vertices.emplace_back(x, y, -0.1); + } + + for (unsigned int i = 0; i < m_resolution; ++i) + { + double angle = (double)i * step; + double x = int_radius * ::cos(angle); + double y = int_radius * ::sin(angle); + + vertices.emplace_back(x, y, -0.1); + } + + // top face + vertices.emplace_back(0.0, 1.5, 0.1); + vertices.emplace_back(0.0, 1.0, 0.1); + vertices.emplace_back(-1.0, 2.0, 0.1); + vertices.emplace_back(0.0, 3.0, 0.1); + vertices.emplace_back(0.0, 2.5, 0.1); + + for (unsigned int i = 1; i <= m_resolution; ++i) + { + double angle = (double)i * step; + double x = ext_radius * ::sin(angle); + double y = ext_radius * ::cos(angle); + + vertices.emplace_back(x, y, 0.1); + } + + for (unsigned int i = 0; i < m_resolution; ++i) + { + double angle = (double)i * step; + double x = int_radius * ::cos(angle); + double y = int_radius * ::sin(angle); + + vertices.emplace_back(x, y, 0.1); + } + + // bottom face + triangles.emplace_back(0, 1, 2); + triangles.emplace_back(0, 2, 4); + triangles.emplace_back(4, 2, 3); + + int first_id = 4; + int last_id = (int)vertices_per_level; + triangles.emplace_back(last_id, 0, first_id); + triangles.emplace_back(last_id, first_id, first_id + 1); + for (unsigned int i = 1; i < m_resolution; ++i) + { + triangles.emplace_back(last_id - i, last_id - i + 1, first_id + i); + triangles.emplace_back(last_id - i, first_id + i, first_id + i + 1); + } + + // top face + last_id += 1; + triangles.emplace_back(last_id + 0, last_id + 2, last_id + 1); + triangles.emplace_back(last_id + 0, last_id + 4, last_id + 2); + triangles.emplace_back(last_id + 4, last_id + 3, last_id + 2); + + first_id = last_id + 4; + last_id = last_id + 4 + 2 * (int)m_resolution; + triangles.emplace_back(last_id, first_id, (int)vertices_per_level + 1); + triangles.emplace_back(last_id, first_id + 1, first_id); + for (unsigned int i = 1; i < m_resolution; ++i) + { + triangles.emplace_back(last_id - i, first_id + i, last_id - i + 1); + triangles.emplace_back(last_id - i, first_id + i + 1, first_id + i); + } + + // side face + for (unsigned int i = 0; i < 4 + 2 * (int)m_resolution; ++i) + { + triangles.emplace_back(i, vertices_per_level + 2 + i, i + 1); + triangles.emplace_back(i, vertices_per_level + 1 + i, vertices_per_level + 2 + i); + } + triangles.emplace_back(vertices_per_level, vertices_per_level + 1, 0); + triangles.emplace_back(vertices_per_level, 2 * vertices_per_level + 1, vertices_per_level + 1); + + m_useVBOs = useVBOs; + + if (m_useVBOs) + m_volume.indexed_vertex_array.load_mesh_full_shading(TriangleMesh(vertices, triangles)); + else + m_volume.indexed_vertex_array.load_mesh_flat_shading(TriangleMesh(vertices, triangles)); + + m_volume.finalize_geometry(m_useVBOs); + return true; +} #endif // ENABLE_SIDEBAR_VISUAL_HINTS std::string _3DScene::get_gl_info(bool format_as_html, bool extensions) diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 22b80d627..76cacabbd 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -595,7 +595,13 @@ public: bool init(bool useVBOs) { return on_init(useVBOs); } - void set_color(float* color, unsigned int size); + void set_color(const float* color, unsigned int size); + + const Vec3d& get_offset() const; + void set_offset(const Vec3d& offset); + const Vec3d& get_rotation() const; + void set_rotation(const Vec3d& rotation); + const Vec3d& get_scale() const; void set_scale(const Vec3d& scale); void render() const; @@ -605,12 +611,21 @@ protected: private: void render_VBOs() const; + void render_legacy() const; }; class GLArrow : public GLModel { +protected: + virtual bool on_init(bool useVBOs); +}; + +class GLCurvedArrow : public GLModel +{ + unsigned int m_resolution; + public: - GLArrow(); + explicit GLCurvedArrow(unsigned int resolution); protected: virtual bool on_init(bool useVBOs); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 66a0884a4..44ac1bc71 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -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(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(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 diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index 222ed147e..5911c8a02 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -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; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 3c4debba0..66d4622fe 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -73,6 +73,11 @@ static const float DEFAULT_BG_LIGHT_COLOR[3] = { 0.753f, 0.753f, 0.753f }; static const float ERROR_BG_DARK_COLOR[3] = { 0.478f, 0.192f, 0.039f }; static const float ERROR_BG_LIGHT_COLOR[3] = { 0.753f, 0.192f, 0.039f }; +#if ENABLE_SIDEBAR_VISUAL_HINTS +static const float UNIFORM_SCALE_COLOR[3] = { 1.0f, 0.38f, 0.0f }; +static const float AXES_COLOR[3][3] = { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }; +#endif // ENABLE_SIDEBAR_VISUAL_HINTS + namespace Slic3r { namespace GUI { @@ -1154,12 +1159,6 @@ GLCanvas3D::Selection::VolumeCache::VolumeCache(const Vec3d& position, const Vec } #endif // ENABLE_MODELVOLUME_TRANSFORM -#if ENABLE_SIDEBAR_VISUAL_HINTS -const float GLCanvas3D::Selection::RED[3] = { 1.0f, 0.0f, 0.0f }; -const float GLCanvas3D::Selection::GREEN[3] = { 0.0f, 1.0f, 0.0f }; -const float GLCanvas3D::Selection::BLUE[3] = { 0.0f, 0.0f, 1.0f }; -#endif // ENABLE_SIDEBAR_VISUAL_HINTS - GLCanvas3D::Selection::Selection() : m_volumes(nullptr) , m_model(nullptr) @@ -1167,6 +1166,7 @@ GLCanvas3D::Selection::Selection() , m_type(Empty) , m_valid(false) , m_bounding_box_dirty(true) + , m_curved_arrow(16) { #if ENABLE_RENDER_SELECTION_CENTER m_quadric = ::gluNewQuadric(); @@ -1192,13 +1192,16 @@ void GLCanvas3D::Selection::set_volumes(GLVolumePtrs* volumes) #if ENABLE_SIDEBAR_VISUAL_HINTS bool GLCanvas3D::Selection::init(bool useVBOs) { - if (m_arrow.init(useVBOs)) - { - m_arrow.set_scale(5.0 * Vec3d::Ones()); - return true; - } + if (!m_arrow.init(useVBOs)) + return false; - return false; + m_arrow.set_scale(5.0 * Vec3d::Ones()); + + if (!m_curved_arrow.init(useVBOs)) + return false; + + m_curved_arrow.set_scale(5.0 * Vec3d::Ones()); + return true; } #endif // ENABLE_SIDEBAR_VISUAL_HINTS @@ -2094,14 +2097,22 @@ void GLCanvas3D::Selection::render_sidebar_hints(const std::string& sidebar_fiel else if (is_single_volume() || is_single_modifier()) { const GLVolume* volume = (*m_volumes)[*m_list.begin()]; - Transform3d orient_matrix = volume->get_instance_transformation().get_matrix(true, false, true, true) * volume->get_volume_transformation().get_matrix(true, false, true, true); + Transform3d orient_matrix = volume->get_instance_transformation().get_matrix(true, false, true, true); const Vec3d& offset = get_bounding_box().center(); ::glTranslated(offset(0), offset(1), offset(2)); ::glMultMatrixd(orient_matrix.data()); } else + { ::glTranslated(center(0), center(1), center(2)); + if (requires_local_axes()) + { + const GLVolume* volume = (*m_volumes)[*m_list.begin()]; + Transform3d orient_matrix = volume->get_instance_transformation().get_matrix(true, false, true, true); + ::glMultMatrixd(orient_matrix.data()); + } + } if (boost::starts_with(sidebar_field, "position")) _render_sidebar_position_hints(sidebar_field); @@ -2544,6 +2555,18 @@ void GLCanvas3D::Selection::_render_sidebar_position_hints(const std::string& si void GLCanvas3D::Selection::_render_sidebar_rotation_hints(const std::string& sidebar_field) const { + if (boost::ends_with(sidebar_field, "x")) + { + ::glRotated(90.0, 0.0, 1.0, 0.0); + _render_sidebar_rotation_hint(X); + } + else if (boost::ends_with(sidebar_field, "y")) + { + ::glRotated(-90.0, 1.0, 0.0, 0.0); + _render_sidebar_rotation_hint(Y); + } + else if (boost::ends_with(sidebar_field, "z")) + _render_sidebar_rotation_hint(Z); } void GLCanvas3D::Selection::_render_sidebar_scale_hints(const std::string& sidebar_field) const @@ -2579,33 +2602,22 @@ void GLCanvas3D::Selection::_render_sidebar_size_hints(const std::string& sideba void GLCanvas3D::Selection::_render_sidebar_position_hint(Axis axis) const { - float color[3]; - switch (axis) - { - case X: { ::memcpy((void*)color, (const void*)RED, 3 * sizeof(float)); break; } - case Y: { ::memcpy((void*)color, (const void*)GREEN, 3 * sizeof(float)); break; } - case Z: { ::memcpy((void*)color, (const void*)BLUE, 3 * sizeof(float)); break; } - } - - m_arrow.set_color(color, 3); + m_arrow.set_color(AXES_COLOR[axis], 3); m_arrow.render(); } -void GLCanvas3D::Selection::_render_sidebar_rotation_hint(Axis axis, double length) const +void GLCanvas3D::Selection::_render_sidebar_rotation_hint(Axis axis) const { + m_curved_arrow.set_color(AXES_COLOR[axis], 3); + m_curved_arrow.render(); + + ::glRotated(180.0, 0.0, 0.0, 1.0); + m_curved_arrow.render(); } void GLCanvas3D::Selection::_render_sidebar_scale_hint(Axis axis) const { - float color[3]; - switch (axis) - { - case X: { ::memcpy((void*)color, (const void*)RED, 3 * sizeof(float)); break; } - case Y: { ::memcpy((void*)color, (const void*)GREEN, 3 * sizeof(float)); break; } - case Z: { ::memcpy((void*)color, (const void*)BLUE, 3 * sizeof(float)); break; } - } - - m_arrow.set_color(color, 3); + m_arrow.set_color((requires_uniform_scale() ? UNIFORM_SCALE_COLOR : AXES_COLOR[axis]), 3); ::glTranslated(0.0, 5.0, 0.0); m_arrow.render(); @@ -3818,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) @@ -4132,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; @@ -6310,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) { diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 413d625f1..0cd6cb9b9 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -369,12 +369,6 @@ class GLCanvas3D public: class Selection { -#if ENABLE_SIDEBAR_VISUAL_HINTS - static const float RED[3]; - static const float GREEN[3]; - static const float BLUE[3]; -#endif // ENABLE_SIDEBAR_VISUAL_HINTS - public: typedef std::set IndicesList; @@ -504,6 +498,7 @@ public: #endif // ENABLE_RENDER_SELECTION_CENTER #if ENABLE_SIDEBAR_VISUAL_HINTS mutable GLArrow m_arrow; + mutable GLCurvedArrow m_curved_arrow; #endif // ENABLE_SIDEBAR_VISUAL_HINTS public: @@ -619,7 +614,7 @@ public: void _render_sidebar_scale_hints(const std::string& sidebar_field) const; void _render_sidebar_size_hints(const std::string& sidebar_field) const; void _render_sidebar_position_hint(Axis axis) const; - void _render_sidebar_rotation_hint(Axis axis, double length) const; + void _render_sidebar_rotation_hint(Axis axis) const; void _render_sidebar_scale_hint(Axis axis) const; void _render_sidebar_size_hint(Axis axis, double length) const; #endif // ENABLE_SIDEBAR_VISUAL_HINTS @@ -855,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; @@ -955,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); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 2998ea7f3..74ea52c64 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -43,7 +43,7 @@ namespace GUI { wxString file_wildcards(FileType file_type, const std::string &custom_extension) { - static const wxString defaults[FT_SIZE] = { + static const std::string defaults[FT_SIZE] = { /* FT_STL */ "STL files (*.stl)|*.stl;*.STL", /* FT_OBJ */ "OBJ files (*.obj)|*.obj;*.OBJ", /* FT_AMF */ "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML", @@ -57,13 +57,17 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension) /* FT_PNGZIP */"Zipped PNG files (*.zip)|*.zip;*.ZIP", // This is lame, but that's what we use for SLA }; - wxString out = defaults[file_type]; + std::string out = defaults[file_type]; if (! custom_extension.empty()) { - // Append the custom extension to the wildcards, so that the file dialog would not add the default extension to it. - out += ";*"; - out += from_u8(custom_extension); + // Find the custom extension in the template. + if (out.find(std::string("*") + custom_extension + ",") == std::string::npos && out.find(std::string("*") + custom_extension + ")") == std::string::npos) { + // The custom extension was not found in the template. + // Append the custom extension to the wildcards, so that the file dialog would not add the default extension to it. + boost::replace_first(out, ")|", std::string(", *") + custom_extension + ")|"); + out += std::string(";*") + custom_extension; + } } - return out; + return wxString::FromUTF8(out.c_str()); } static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); } @@ -137,7 +141,7 @@ bool GUI_App::OnInit() std::cerr << "Creating main frame..." << std::endl; if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) wxImage::AddHandler(new wxPNGHandler()); - mainframe = new MainFrame(no_plater, false); + mainframe = new MainFrame(); sidebar().obj_list()->init_objects(); // propagate model objects to object list update_mode(); SetTopWindow(mainframe); @@ -177,6 +181,9 @@ bool GUI_App::OnInit() if (app_config->dirty()) app_config->save(); + + if (this->plater() != nullptr) + this->obj_manipul()->update_if_dirty(); }); // On OS X the UI tends to freeze in weird ways if modal dialogs(config wizard, update notifications, ...) @@ -277,8 +284,8 @@ void GUI_App::recreate_GUI() { std::cerr << "recreate_GUI" << std::endl; - auto topwindow = GetTopWindow(); - mainframe = new MainFrame(no_plater,false); + MainFrame* topwindow = dynamic_cast(GetTopWindow()); + mainframe = new MainFrame(); sidebar().obj_list()->init_objects(); // propagate model objects to object list update_mode(); @@ -287,6 +294,20 @@ void GUI_App::recreate_GUI() topwindow->Destroy(); } + m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); + + CallAfter([this]() { + // temporary workaround for the correct behavior of the Scrolled sidebar panel + auto& panel = sidebar(); + if (panel.obj_list()->GetMinHeight() > 200) { + wxWindowUpdateLocker noUpdates_sidebar(&panel); + panel.obj_list()->SetMinSize(wxSize(-1, 200)); + panel.Layout(); + } + }); + + mainframe->Show(true); + // On OSX the UI was not initialized correctly if the wizard was called // before the UI was up and running. CallAfter([]() { diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 81175b7ca..bd64a3ac5 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -71,7 +71,6 @@ static wxString dots("…", wxConvUTF8); class GUI_App : public wxApp { - bool no_plater{ false }; bool app_conf_exists{ false }; // Lock to guard the callback stack diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 8cc2362e8..7955326f5 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -47,6 +47,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_og->m_fill_empty_value = [this](const std::string& opt_key) { + this->update_if_dirty(); + std::string param; std::copy(opt_key.begin(), opt_key.end() - 2, std::back_inserter(param)); @@ -83,6 +85,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_og->m_set_focus = [this](const std::string& opt_key) { + this->update_if_dirty(); wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, true); }; @@ -179,22 +182,19 @@ bool ObjectManipulation::IsShown() void ObjectManipulation::UpdateAndShow(const bool show) { - if (show) + if (show) { update_settings_value(wxGetApp().plater()->canvas3D()->get_selection()); + update_if_dirty(); + } OG_Settings::UpdateAndShow(show); } -int ObjectManipulation::ol_selection() -{ - return wxGetApp().obj_list()->get_selected_obj_idx(); -} - void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& selection) { - wxString move_label = _(L("Position:")); - wxString rotate_label = _(L("Rotation:")); - wxString scale_label = _(L("Scale factors:")); + m_new_move_label_string = L("Position:"); + m_new_rotate_label_string = L("Rotation:"); + m_new_scale_label_string = L("Scale factors:"); #if ENABLE_MODELVOLUME_TRANSFORM if (selection.is_single_full_instance()) #else @@ -205,10 +205,10 @@ void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& sele { // all volumes in the selection belongs to the same instance, any of them contains the needed data, so we take the first const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - update_position_value(volume->get_offset()); - update_rotation_value(volume->get_rotation()); - update_scale_value(volume->get_scaling_factor()); - m_og->enable(); + m_new_position = volume->get_offset(); + m_new_rotation = volume->get_rotation(); + m_new_scale = volume->get_scaling_factor(); + m_new_enabled = true; } else reset_settings_value(); @@ -219,143 +219,104 @@ void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& sele // all volumes in the selection belongs to the same instance, any of them contains the needed data, so we take the first const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); #if ENABLE_MODELVOLUME_TRANSFORM - update_position_value(volume->get_instance_offset()); - update_rotation_value(volume->get_instance_rotation()); - update_scale_value(volume->get_instance_scaling_factor()); - update_size_value(volume->get_instance_transformation().get_matrix(true, true) * volume->bounding_box.size()); + m_new_position = volume->get_instance_offset(); + m_new_rotation = volume->get_instance_rotation(); + m_new_scale = volume->get_instance_scaling_factor(); + m_new_size = volume->get_instance_transformation().get_matrix(true, true) * volume->bounding_box.size(); #else - update_position_value(volume->get_offset()); - update_rotation_value(volume->get_rotation()); - update_scale_value(volume->get_scaling_factor()); + m_new_position = volume->get_offset(); + m_new_rotation = volume->get_rotation(); + m_new_scale = volume->get_scaling_factor(); #endif // ENABLE_MODELVOLUME_TRANSFORM - m_og->enable(); + m_new_enabled = true; } else if (selection.is_single_full_object()) { const BoundingBoxf3& box = selection.get_bounding_box(); - update_position_value(box.center()); - reset_rotation_value(); - reset_scale_value(); - update_size_value(box.size()); - rotate_label = _(L("Rotate:")); - scale_label = _(L("Scale:")); - m_og->enable(); + m_new_position = box.center(); + m_new_rotation = Vec3d::Zero(); + m_new_scale = Vec3d(1.0, 1.0, 1.0); + m_new_size = box.size(); + m_new_rotate_label_string = L("Rotate:"); + m_new_scale_label_string = L("Scale:"); + m_new_enabled = true; } else if (selection.is_single_modifier() || selection.is_single_volume()) { // the selection contains a single volume const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); #if ENABLE_MODELVOLUME_TRANSFORM - update_position_value(volume->get_volume_offset()); - update_rotation_value(volume->get_volume_rotation()); - update_scale_value(volume->get_volume_scaling_factor()); - update_size_value(volume->bounding_box.size()); + m_new_position = volume->get_volume_offset(); + m_new_rotation = volume->get_volume_rotation(); + m_new_scale = volume->get_volume_scaling_factor(); + m_new_size = volume->bounding_box.size(); #else - update_position_value(volume->get_offset()); - update_rotation_value(volume->get_rotation()); - update_scale_value(volume->get_scaling_factor()); + m_new_position = volume->get_offset(); + m_new_rotation = volume->get_rotation(); + m_new_scale = volume->get_scaling_factor(); #endif // ENABLE_MODELVOLUME_TRANSFORM - m_og->enable(); + m_new_enabled = true; } else if (wxGetApp().obj_list()->multiple_selection()) { reset_settings_value(); - move_label = _(L("Translate:")); - rotate_label = _(L("Rotate:")); - scale_label = _(L("Scale:")); - update_size_value(selection.get_bounding_box().size()); - m_og->enable(); + m_new_move_label_string = L("Translate:"); + m_new_rotate_label_string = L("Rotate:"); + m_new_scale_label_string = L("Scale:"); + m_new_size = selection.get_bounding_box().size(); + m_new_enabled = true; } else reset_settings_value(); - m_move_Label->SetLabel(move_label); - m_rotate_Label->SetLabel(rotate_label); - m_scale_Label->SetLabel(scale_label); + m_dirty = true; +} + +void ObjectManipulation::update_if_dirty() +{ + if (! m_dirty) + return; + + m_move_Label->SetLabel(_(m_new_move_label_string)); + m_rotate_Label->SetLabel(_(m_new_rotate_label_string)); + m_scale_Label->SetLabel(_(m_new_scale_label_string)); + + m_og->set_value("position_x", double_to_string(m_new_position(0), 2)); + m_og->set_value("position_y", double_to_string(m_new_position(1), 2)); + m_og->set_value("position_z", double_to_string(m_new_position(2), 2)); + cache_position = m_new_position; + + auto scale = m_new_scale * 100.0; + m_og->set_value("scale_x", double_to_string(scale(0), 2)); + m_og->set_value("scale_y", double_to_string(scale(1), 2)); + m_og->set_value("scale_z", double_to_string(scale(2), 2)); + cache_scale = scale; + + m_og->set_value("size_x", double_to_string(m_new_size(0), 2)); + m_og->set_value("size_y", double_to_string(m_new_size(1), 2)); + m_og->set_value("size_z", double_to_string(m_new_size(2), 2)); + cache_size = m_new_size; + + m_og->set_value("rotation_x", double_to_string(round_nearest(Geometry::rad2deg(m_new_rotation(0)), 0), 2)); + m_og->set_value("rotation_y", double_to_string(round_nearest(Geometry::rad2deg(m_new_rotation(1)), 0), 2)); + m_og->set_value("rotation_z", double_to_string(round_nearest(Geometry::rad2deg(m_new_rotation(2)), 0), 2)); + cache_rotation = m_new_rotation; + + if (m_new_enabled) + m_og->enable(); + else + m_og->disable(); + + m_dirty = false; } void ObjectManipulation::reset_settings_value() { - reset_position_value(); - reset_rotation_value(); - reset_scale_value(); - m_og->disable(); -} - -wxString def_0 {"0"}; -wxString def_100 {"100"}; - -void ObjectManipulation::reset_position_value() -{ - m_og->set_value("position_x", def_0); - m_og->set_value("position_y", def_0); - m_og->set_value("position_z", def_0); - - cache_position = Vec3d::Zero(); -} - -void ObjectManipulation::reset_rotation_value() -{ - m_og->set_value("rotation_x", def_0); - m_og->set_value("rotation_y", def_0); - m_og->set_value("rotation_z", def_0); - - cache_rotation = Vec3d::Zero(); -} - -void ObjectManipulation::reset_scale_value() -{ - m_og->set_value("scale_x", def_100); - m_og->set_value("scale_y", def_100); - m_og->set_value("scale_z", def_100); - - cache_scale = Vec3d(100.0, 100.0, 100.0); -} - -void ObjectManipulation::reset_size_value() -{ - m_og->set_value("size_x", def_0); - m_og->set_value("size_y", def_0); - m_og->set_value("size_z", def_0); - - cache_size = Vec3d::Zero(); -} - -void ObjectManipulation::update_position_value(const Vec3d& position) -{ - m_og->set_value("position_x", double_to_string(position(0), 2)); - m_og->set_value("position_y", double_to_string(position(1), 2)); - m_og->set_value("position_z", double_to_string(position(2), 2)); - - cache_position = position; -} - -void ObjectManipulation::update_scale_value(const Vec3d& scaling_factor) -{ - auto scale = scaling_factor * 100.0; - m_og->set_value("scale_x", double_to_string(scale(0), 2)); - m_og->set_value("scale_y", double_to_string(scale(1), 2)); - m_og->set_value("scale_z", double_to_string(scale(2), 2)); - - cache_scale = scale; -} - -void ObjectManipulation::update_size_value(const Vec3d& size) -{ - m_og->set_value("size_x", double_to_string(size(0), 2)); - m_og->set_value("size_y", double_to_string(size(1), 2)); - m_og->set_value("size_z", double_to_string(size(2), 2)); - - cache_size = size; -} - -void ObjectManipulation::update_rotation_value(const Vec3d& rotation) -{ - m_og->set_value("rotation_x", double_to_string(round_nearest(Geometry::rad2deg(rotation(0)), 0), 2)); - m_og->set_value("rotation_y", double_to_string(round_nearest(Geometry::rad2deg(rotation(1)), 0), 2)); - m_og->set_value("rotation_z", double_to_string(round_nearest(Geometry::rad2deg(rotation(2)), 0), 2)); - - cache_rotation = rotation; + m_new_position = Vec3d::Zero(); + m_new_rotation = Vec3d::Zero(); + m_new_scale = Vec3d(1.0, 1.0, 1.0); + m_new_size = Vec3d::Zero(); + m_new_enabled = false; } void ObjectManipulation::change_position_value(const Vec3d& position) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 0ada37b96..cbac94058 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -23,6 +23,19 @@ class ObjectManipulation : public OG_Settings wxStaticText* m_scale_Label = nullptr; wxStaticText* m_rotate_Label = nullptr; + + // Needs to be updated from OnIdle? + bool m_dirty = false; + // Cached labels for the delayed update, not localized! + std::string m_new_move_label_string; + std::string m_new_rotate_label_string; + std::string m_new_scale_label_string; + Vec3d m_new_position; + Vec3d m_new_rotation; + Vec3d m_new_scale; + Vec3d m_new_size; + bool m_new_enabled; + public: ObjectManipulation(wxWindow* parent); ~ObjectManipulation() {} @@ -31,19 +44,14 @@ public: bool IsShown() override; void UpdateAndShow(const bool show) override; - int ol_selection(); + void update_settings_value(const GLCanvas3D::Selection& selection); - void update_settings_value(const GLCanvas3D::Selection& selection); + // Called from the App to update the UI if dirty. + void update_if_dirty(); + +private: void reset_settings_value(); - void reset_position_value(); - void reset_rotation_value(); - void reset_scale_value(); - void reset_size_value(); - // update position values displacements or "gizmos" - void update_position_value(const Vec3d& position); - // update scale values after scale unit changing or "gizmos" - void update_scale_value(const Vec3d& scaling_factor); // update size values after scale unit changing or "gizmos" void update_size_value(const Vec3d& size); // update rotation value after "gizmos" diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 735b55125..459bd9e5b 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -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 @@ -214,7 +213,6 @@ Preview::Preview(wxNotebook* notebook, DynamicPrintConfig* config, BackgroundSli , m_preferred_color_mode("feature") , m_loaded(false) , m_enabled(false) - , m_force_sliders_full_range(false) , m_schedule_background_process(schedule_background_process_func) { #if ENABLE_REMOVE_TABS_FROM_PLATER @@ -258,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); @@ -514,14 +511,14 @@ void Preview::show_hide_ui_elements(const std::string& what) void Preview::reset_sliders() { m_enabled = false; - reset_double_slider(); +// reset_double_slider(); m_double_slider_sizer->Hide((size_t)0); } void Preview::update_sliders(const std::vector& layers_z) { m_enabled = true; - update_double_slider(layers_z, m_force_sliders_full_range); + update_double_slider(layers_z); m_double_slider_sizer->Show((size_t)0); Layout(); } @@ -587,7 +584,8 @@ void Preview::create_double_slider() auto& config = wxGetApp().preset_bundle->project_config; ((config.option("colorprint_heights"))->values) = (m_slider->GetTicksValues()); m_schedule_background_process(); - int type = m_choice_view_type->FindString(_(L("Color Print"))); + bool color_print = !config.option("colorprint_heights")->values.empty(); + int type = m_choice_view_type->FindString(color_print ? _(L("Color Print")) : _(L("Feature type")) ); if (m_choice_view_type->GetSelection() != type) { m_choice_view_type->SetSelection(type); if ((0 <= type) && (type < (int)GCodePreviewData::Extrusion::Num_View_Types)) @@ -597,26 +595,70 @@ void Preview::create_double_slider() }); } +// Find an index of a value in a sorted vector, which is in . +// Returns -1 if there is no such member. +static int find_close_layer_idx(const std::vector& zs, double &z, double eps) +{ + if (zs.empty()) + return -1; + auto it_h = std::lower_bound(zs.begin(), zs.end(), z); + if (it_h == zs.end()) { + auto it_l = it_h; + -- it_l; + if (z - *it_l < eps) + return int(zs.size() - 1); + } else if (it_h == zs.begin()) { + if (*it_h - z < eps) + return 0; + } else { + auto it_l = it_h; + -- it_l; + double dist_l = z - *it_l; + double dist_h = *it_h - z; + if (std::min(dist_l, dist_h) < eps) { + return (dist_l < dist_h) ? int(it_l - zs.begin()) : int(it_h - zs.begin()); + } + } + return -1; +} + void Preview::update_double_slider(const std::vector& layers_z, bool force_sliders_full_range) { + // Save the initial slider span. + double z_low = m_slider->GetLowerValueD(); + double z_high = m_slider->GetHigherValueD(); + bool was_empty = m_slider->GetMaxValue() == 0; + bool span_changed = layers_z.empty() || std::abs(layers_z.back() - m_slider->GetMaxValueD()) > 1e-6; + force_sliders_full_range |= was_empty | span_changed; + bool snap_to_min = force_sliders_full_range || m_slider->is_lower_at_min(); + bool snap_to_max = force_sliders_full_range || m_slider->is_higher_at_max(); + std::vector> values; fill_slider_values(values, layers_z); - - m_slider->SetMaxValue(layers_z.size() - 1); - if (force_sliders_full_range) - m_slider->SetHigherValue(layers_z.size() - 1); - m_slider->SetSliderValues(values); - const double z_low = m_slider->GetLowerValueD(); - const double z_high = m_slider->GetHigherValueD(); + assert(m_slider->GetMinValue() == 0); + m_slider->SetMaxValue(layers_z.empty() ? 0 : layers_z.size() - 1); + + int idx_low = 0; + int idx_high = m_slider->GetMaxValue(); + if (! layers_z.empty()) { + if (! snap_to_min) { + int idx_new = find_close_layer_idx(layers_z, z_low, 1e-6); + if (idx_new != -1) + idx_low = idx_new; + } + if (! snap_to_max) { + int idx_new = find_close_layer_idx(layers_z, z_high, 1e-6); + if (idx_new != -1) + idx_high = idx_new; + } + } + m_slider->SetSelectionSpan(idx_low, idx_high); const auto& config = wxGetApp().preset_bundle->project_config; const std::vector &ticks_from_config = (config.option("colorprint_heights"))->values; - m_slider->SetTicksValues(ticks_from_config); - set_double_slider_thumbs(layers_z, z_low, z_high); - bool color_print_enable = (wxGetApp().plater()->printer_technology() == ptFFF); if (color_print_enable) { const auto& config = wxGetApp().preset_bundle->full_config(); @@ -638,8 +680,7 @@ void Preview::fill_slider_values(std::vector> &values, // All ticks that would end up outside the slider range should be erased. // TODO: this should be placed into more appropriate part of code, // this function is e.g. not called when the last object is deleted - auto& config = wxGetApp().preset_bundle->project_config; - std::vector &ticks_from_config = (config.option("colorprint_heights"))->values; + std::vector &ticks_from_config = (wxGetApp().preset_bundle->project_config.option("colorprint_heights"))->values; unsigned int old_size = ticks_from_config.size(); ticks_from_config.erase(std::remove_if(ticks_from_config.begin(), ticks_from_config.end(), [values](double val) { return values.back().second < val; }), @@ -648,32 +689,6 @@ void Preview::fill_slider_values(std::vector> &values, m_schedule_background_process(); } -void Preview::set_double_slider_thumbs(const std::vector &layers_z, - const double z_low, - const double z_high) -{ - // Force slider full range only when slider is created. - // Support selected diapason on the all next steps - if (z_high == 0.0) { - m_slider->SetLowerValue(0); - m_slider->SetHigherValue(layers_z.size() - 1); - return; - } - - for (int i = layers_z.size() - 1; i >= 0; i--) -// if (z_low >= layers_z[i]) { - if (fabs(z_low - layers_z[i]) <= 1e-6) { - m_slider->SetLowerValue(i); - break; - } - for (int i = layers_z.size() - 1; i >= 0; i--) -// if (z_high >= layers_z[i]) { - if (fabs(z_high-layers_z[i]) <= 1e-6) { - m_slider->SetHigherValue(i); - break; - } -} - void Preview::reset_double_slider() { m_slider->SetHigherValue(0); @@ -692,7 +707,7 @@ void Preview::update_double_slider_from_canvas(wxKeyEvent& event) if (key == 'U' || key == 'D') { const int new_pos = key == 'U' ? m_slider->GetHigherValue() + 1 : m_slider->GetHigherValue() - 1; m_slider->SetHigherValue(new_pos); - if (event.ShiftDown()) m_slider->SetLowerValue(m_slider->GetHigherValue()); + if (event.ShiftDown() || m_slider->is_one_layer()) m_slider->SetLowerValue(m_slider->GetHigherValue()); } else if (key == 'S') m_slider->ChangeOneLayerLock(); @@ -778,12 +793,8 @@ void Preview::load_print_as_fff() if (IsShown()) { - // used to set the sliders to the extremes of the current zs range - m_force_sliders_full_range = false; - if (gcode_preview_data_valid) { - m_force_sliders_full_range = (m_canvas->get_volumes_count() == 0); m_canvas->load_gcode_preview(*m_gcode_preview_data, colors); show_hide_ui_elements("full"); @@ -849,7 +860,6 @@ void Preview::load_print_as_sla() { std::vector layer_zs; std::copy(zs.begin(), zs.end(), std::back_inserter(layer_zs)); - m_force_sliders_full_range = true; update_sliders(layer_zs); } diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 534191633..bd71adcb5 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -117,7 +117,6 @@ class Preview : public wxPanel bool m_loaded; bool m_enabled; - bool m_force_sliders_full_range; PrusaDoubleSlider* m_slider {nullptr}; @@ -177,12 +176,9 @@ private: // Create/Update/Reset double slider on 3dPreview void create_double_slider(); - void update_double_slider(const std::vector& layers_z, bool force_sliders_full_range); + void update_double_slider(const std::vector& layers_z, bool force_sliders_full_range = false); void fill_slider_values(std::vector> &values, const std::vector &layers_z); - void set_double_slider_thumbs( const std::vector &layers_z, - const double z_low, - const double z_high); void reset_double_slider(); // update DoubleSlider after keyDown in canvas void update_double_slider_from_canvas(wxKeyEvent& event); diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index b3edbc9a8..12929b2cc 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -3,6 +3,7 @@ #include "libslic3r/Utils.hpp" #include "GUI.hpp" #include +#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"))); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 2211023f0..5b38129de 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -28,10 +28,8 @@ namespace Slic3r { namespace GUI { -MainFrame::MainFrame(const bool no_plater, const bool loaded) : +MainFrame::MainFrame() : wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE), - m_no_plater(no_plater), - m_loaded(loaded), m_printhost_queue_dlg(new PrintHostQueueDialog(this)) { // Load the icon either from the exe, or from the ico file. @@ -125,11 +123,9 @@ void MainFrame::init_tabpanel() } }); - if (!m_no_plater) { - m_plater = new Slic3r::GUI::Plater(m_tabpanel, this); - wxGetApp().plater_ = m_plater; - m_tabpanel->AddPage(m_plater, _(L("Plater"))); - } + m_plater = new Slic3r::GUI::Plater(m_tabpanel, this); + wxGetApp().plater_ = m_plater; + m_tabpanel->AddPage(m_plater, _(L("Plater"))); // The following event is emited by Tab implementation on config value change. Bind(EVT_TAB_VALUE_CHANGED, &MainFrame::on_value_changed, this); @@ -380,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 diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index fab6aea90..e0411b6da 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -42,10 +42,7 @@ struct PresetTab { class MainFrame : public wxFrame { - bool m_no_plater; - bool m_loaded; - int m_lang_ch_event; - int m_preferences_event; + bool m_loaded {false}; wxString m_qs_last_input_file = wxEmptyString; wxString m_qs_last_output_file = wxEmptyString; @@ -71,8 +68,7 @@ class MainFrame : public wxFrame bool can_delete_all() const; public: - MainFrame() {} - MainFrame(const bool no_plater, const bool loaded); + MainFrame(); ~MainFrame() {} Plater* plater() { return m_plater; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index cba53cf17..11894adf8 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1117,6 +1117,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) #if ENABLE_REMOVE_TABS_FROM_PLATER view3D = new View3D(q, &model, config, &background_process); preview = new Preview(q, config, &background_process, &gcode_preview_data, [this](){ schedule_background_process(); }); + // Let the Tab key switch between the 3D view and the layer preview. + view3D->Bind(wxEVT_NAVIGATION_KEY, [this](wxNavigationKeyEvent &evt) { if (evt.IsFromTab()) this->q->select_view_3D("Preview"); }); + preview->Bind(wxEVT_NAVIGATION_KEY, [this](wxNavigationKeyEvent &evt) { if (evt.IsFromTab()) this->q->select_view_3D("3D"); }); panels.push_back(view3D); panels.push_back(preview); @@ -1132,7 +1135,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 +1164,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 +1253,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) diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index 8ac8615a8..8c0c0fc85 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -14,6 +14,7 @@ #include #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"))); + 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,24 +151,82 @@ 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(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(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(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"); - // TODO + 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(); } diff --git a/src/slic3r/GUI/PrintHostDialogs.hpp b/src/slic3r/GUI/PrintHostDialogs.hpp index e38acee32..ee3fe26d8 100644 --- a/src/slic3r/GUI/PrintHostDialogs.hpp +++ b/src/slic3r/GUI/PrintHostDialogs.hpp @@ -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); }} diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index f7f1cfedb..110bfaf44 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -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); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 5a212d4ee..a4796778a 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1601,8 +1601,9 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) optgroup->append_line(host_line); optgroup->append_single_option_line("printhost_apikey"); - if (Http::ca_file_supported()) { + const auto ca_file_hint = _(L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate.")); + if (Http::ca_file_supported()) { Line cafile_line = optgroup->create_single_option_line("printhost_cafile"); auto printhost_cafile_browse = [this, optgroup] (wxWindow* parent) { @@ -1625,19 +1626,31 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) cafile_line.append_widget(printhost_cafile_browse); optgroup->append_line(cafile_line); - auto printhost_cafile_hint = [this, optgroup] (wxWindow* parent) { - auto txt = new wxStaticText(parent, wxID_ANY, - _(L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate."))); + Line cafile_hint { "", "" }; + cafile_hint.full_width = 1; + cafile_hint.widget = [this, ca_file_hint](wxWindow* parent) { + auto txt = new wxStaticText(parent, wxID_ANY, ca_file_hint); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(txt); + return sizer; + }; + optgroup->append_line(cafile_hint); + } else { + Line line { "", "" }; + line.full_width = 1; + + line.widget = [this, ca_file_hint] (wxWindow* parent) { + auto txt = new wxStaticText(parent, wxID_ANY, wxString::Format("%s\n\n\t%s", + _(L("HTTPS CA File:\n\ +\tOn this system, Slic3r uses HTTPS certificates from the system Certificate Store or Keychain.\n\ +\tTo use a custom CA file, please import your CA file into Certificate Store / Keychain.")), + ca_file_hint)); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(txt); return sizer; }; - Line cafile_hint { "", "" }; - cafile_hint.full_width = 1; - cafile_hint.widget = std::move(printhost_cafile_hint); - optgroup->append_line(cafile_hint); - + optgroup->append_line(line); } } diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 2daba5df4..dc917f875 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -1506,6 +1506,21 @@ void PrusaDoubleSlider::SetHigherValue(const int higher_val) ProcessWindowEvent(e); } +void PrusaDoubleSlider::SetSelectionSpan(const int lower_val, const int higher_val) +{ + m_lower_value = std::max(lower_val, m_min_value); + m_higher_value = std::max(std::min(higher_val, m_max_value), m_lower_value); + if (m_lower_value < m_higher_value) + m_is_one_layer = false; + + Refresh(); + Update(); + + wxCommandEvent e(wxEVT_SCROLL_CHANGED); + e.SetEventObject(this); + ProcessWindowEvent(e); +} + void PrusaDoubleSlider::SetMaxValue(const int max_value) { m_max_value = max_value; @@ -2043,7 +2058,7 @@ void PrusaDoubleSlider::enter_window(wxMouseEvent& event, const bool enter) // - value decrease (if wxSL_HORIZONTAL) void PrusaDoubleSlider::move_current_thumb(const bool condition) { - m_is_one_layer = wxGetKeyState(WXK_CONTROL); +// m_is_one_layer = wxGetKeyState(WXK_CONTROL); int delta = condition ? -1 : 1; if (is_horizontal()) delta *= -1; diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index e8fba1ea2..b951c5635 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -697,18 +697,20 @@ public: const wxString& name = wxEmptyString); ~PrusaDoubleSlider() {} - int GetLowerValue() const { - return m_lower_value; - } - int GetHigherValue() const { - return m_higher_value; - } + int GetMinValue() const { return m_min_value; } + int GetMaxValue() const { return m_max_value; } + double GetMinValueD() { return m_values.empty() ? 0. : m_values[m_min_value].second; } + double GetMaxValueD() { return m_values.empty() ? 0. : m_values[m_max_value].second; } + int GetLowerValue() const { return m_lower_value; } + int GetHigherValue() const { return m_higher_value; } int GetActiveValue() const; double GetLowerValueD() { return get_double_value(ssLower); } double GetHigherValueD() { return get_double_value(ssHigher); } wxSize DoGetBestSize() const override; void SetLowerValue(const int lower_val); void SetHigherValue(const int higher_val); + // Set low and high slider position. If the span is non-empty, disable the "one layer" mode. + void SetSelectionSpan(const int lower_val, const int higher_val); void SetMaxValue(const int max_value); void SetKoefForLabels(const double koef) { m_label_koef = koef; @@ -726,6 +728,12 @@ public: EnableTickManipulation(false); } + bool is_horizontal() const { return m_style == wxSL_HORIZONTAL; } + bool is_one_layer() const { return m_is_one_layer; } + bool is_lower_at_min() const { return m_lower_value == m_min_value; } + bool is_higher_at_max() const { return m_higher_value == m_max_value; } + bool is_full_span() const { return this->is_lower_at_min() && this->is_higher_at_max(); } + void OnPaint(wxPaintEvent& ) { render();} void OnLeftDown(wxMouseEvent& event); void OnMotion(wxMouseEvent& event); @@ -762,7 +770,6 @@ protected: bool is_point_in_rect(const wxPoint& pt, const wxRect& rect); int is_point_near_tick(const wxPoint& pt); - bool is_horizontal() const { return m_style == wxSL_HORIZONTAL; } double get_scroll_step(); wxString get_label(const SelectedSlider& selection) const; diff --git a/src/slic3r/Utils/Duet.cpp b/src/slic3r/Utils/Duet.cpp index 1772ae8ef..3449e610e 100644 --- a/src/slic3r/Utils/Duet.cpp +++ b/src/slic3r/Utils/Duet.cpp @@ -46,7 +46,7 @@ bool Duet::test(wxString &msg) const wxString Duet::get_test_ok_msg () const { - return wxString::Format("%s", _(L("Connection to Duet works correctly."))); + return _(L("Connection to Duet works correctly.")); } wxString Duet::get_test_failed_msg (wxString &msg) const @@ -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, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const +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(); diff --git a/src/slic3r/Utils/Duet.hpp b/src/slic3r/Utils/Duet.hpp index d0f5b3009..e1c28d149 100644 --- a/src/slic3r/Utils/Duet.hpp +++ b/src/slic3r/Utils/Duet.hpp @@ -22,10 +22,11 @@ public: virtual bool test(wxString &curl_msg) const; virtual wxString get_test_ok_msg () const; virtual wxString get_test_failed_msg (wxString &msg) const; - virtual bool upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const; + virtual bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const; 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); }; diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index 6e6c9ed44..f7039b6a2 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -32,6 +32,7 @@ class CurlGlobalInit struct Http::priv { enum { + DEFAULT_TIMEOUT = 10, DEFAULT_SIZE_LIMIT = 5 * 1024 * 1024, }; @@ -63,6 +64,7 @@ struct Http::priv static int xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow); static size_t form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp); + void set_timeout(long timeout); void form_add_file(const char *name, const fs::path &path, const char* filename); void set_post_body(const fs::path &path); @@ -84,6 +86,7 @@ Http::priv::priv(const std::string &url) throw std::runtime_error(std::string("Could not construct Curl object")); } + set_timeout(DEFAULT_TIMEOUT); ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // curl makes a copy internally ::curl_easy_setopt(curl, CURLOPT_USERAGENT, SLIC3R_FORK_NAME "/" SLIC3R_VERSION); ::curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error_buffer.front()); @@ -146,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) @@ -167,6 +172,12 @@ size_t Http::priv::form_file_read_cb(char *buffer, size_t size, size_t nitems, v return stream->gcount(); } +void Http::priv::set_timeout(long timeout) +{ + ::curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, timeout); + ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); +} + void Http::priv::form_add_file(const char *name, const fs::path &path, const char* filename) { // We can't use CURLFORM_FILECONTENT, because curl doesn't support Unicode filenames on Windows @@ -203,10 +214,10 @@ void Http::priv::set_post_body(const fs::path &path) std::string Http::priv::curl_error(CURLcode curlcode) { - return (boost::format("%1% (%2%): %3%") + return (boost::format("%1%:\n%2%\n[Error %3%]") % ::curl_easy_strerror(curlcode) + % error_buffer.c_str() % curlcode - % error_buffer ).str(); } @@ -226,7 +237,9 @@ void Http::priv::http_perform() #if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 32 ::curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xfercb); ::curl_easy_setopt(curl, CURLOPT_XFERINFODATA, static_cast(this)); +#ifndef _WIN32 (void)xfercb_legacy; // prevent unused function warning +#endif #else ::curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, xfercb); ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, static_cast(this)); @@ -269,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 { @@ -293,6 +306,13 @@ Http::~Http() } +Http& Http::timeout(long timeout) +{ + if (timeout < 1) { timeout = priv::DEFAULT_TIMEOUT; } + if (p) { p->set_timeout(timeout); } + return *this; +} + Http& Http::size_limit(size_t sizeLimit) { if (p) { p->limit = sizeLimit; } diff --git a/src/slic3r/Utils/Http.hpp b/src/slic3r/Utils/Http.hpp index fd3f8830d..9406a82f2 100644 --- a/src/slic3r/Utils/Http.hpp +++ b/src/slic3r/Utils/Http.hpp @@ -55,6 +55,8 @@ public: Http& operator=(const Http &) = delete; Http& operator=(Http &&) = delete; + // Sets a maximum connection timeout in seconds + Http& timeout(long timeout); // Sets a maximum size of the data that can be received. // A value of zero sets the default limit, which is is 5MB. Http& size_limit(size_t sizeLimit); diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp index cf5fc2f54..2e2e169b8 100644 --- a/src/slic3r/Utils/OctoPrint.cpp +++ b/src/slic3r/Utils/OctoPrint.cpp @@ -62,7 +62,7 @@ bool OctoPrint::test(wxString &msg) const const auto text = ptree.get_optional("text"); res = validate_version_text(text); if (! res) { - msg = wxString::Format("Mismatched type of print host: %s", text ? *text : "OctoPrint"); + msg = wxString::Format(_(L("Mismatched type of print host: %s")), text ? *text : "OctoPrint"); } } catch (...) { @@ -77,28 +77,24 @@ bool OctoPrint::test(wxString &msg) const wxString OctoPrint::get_test_ok_msg () const { - return wxString::Format("%s", _(L("Connection to OctoPrint works correctly."))); + return _(L("Connection to OctoPrint works correctly.")); } wxString OctoPrint::get_test_failed_msg (wxString &msg) const { return wxString::Format("%s: %s\n\n%s", - _(L("Could not connect to OctoPrint")), msg, _(L("Note: OctoPrint version at least 1.1.0 is required."))); + _(L("Could not connect to OctoPrint")), msg, _(L("Note: OctoPrint version at least 1.1.0 is required."))); } -bool OctoPrint::upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const +bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const { const auto upload_filename = upload_data.upload_path.filename(); const auto upload_parent_path = upload_data.upload_path.parent_path(); wxString test_msg; if (! test(test_msg)) { - - // FIXME: - - // auto errormsg = wxString::Format("%s: %s", errortitle, test_msg); - // GUI::show_error(&progress_dialog, std::move(errormsg)); - // return false; + error_fn(std::move(test_msg)); + return false; } bool res = true; @@ -106,7 +102,7 @@ bool OctoPrint::upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn 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() @@ -122,14 +118,14 @@ bool OctoPrint::upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn }) .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; }) .on_progress([&](Http::Progress progress, bool &cancel) { 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; } }) @@ -175,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 @@ -192,7 +178,7 @@ SLAHost::~SLAHost() {} wxString SLAHost::get_test_ok_msg () const { - return wxString::Format("%s", _(L("Connection to Prusa SLA works correctly."))); + return _(L("Connection to Prusa SLA works correctly.")); } wxString SLAHost::get_test_failed_msg (wxString &msg) const diff --git a/src/slic3r/Utils/OctoPrint.hpp b/src/slic3r/Utils/OctoPrint.hpp index 57aae672a..8da149f53 100644 --- a/src/slic3r/Utils/OctoPrint.hpp +++ b/src/slic3r/Utils/OctoPrint.hpp @@ -23,7 +23,7 @@ public: virtual bool test(wxString &curl_msg) const; virtual wxString get_test_ok_msg () const; virtual wxString get_test_failed_msg (wxString &msg) const; - virtual bool upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const; + virtual bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const; virtual bool has_auto_discovery() const; virtual bool can_test() const; virtual std::string get_host() const { return host; } @@ -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); }; diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 924cf382d..be2a17d66 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -112,7 +113,7 @@ struct PresetUpdater::priv bool get_file(const std::string &url, const fs::path &target_path) const; void prune_tmps() const; void sync_version() const; - void sync_config(const std::set vendors) const; + void sync_config(const std::set vendors); void check_install_indices() const; Updates get_config_updates() const; @@ -130,7 +131,7 @@ PresetUpdater::priv::priv() : { set_download_prefs(GUI::wxGetApp().app_config); check_install_indices(); - index_db = std::move(Index::load_db()); + index_db = Index::load_db(); } // Pull relevant preferences from AppConfig @@ -220,14 +221,14 @@ void PresetUpdater::priv::sync_version() const // Download vendor indices. Also download new bundles if an index indicates there's a new one available. // Both are saved in cache. -void PresetUpdater::priv::sync_config(const std::set vendors) const +void PresetUpdater::priv::sync_config(const std::set vendors) { BOOST_LOG_TRIVIAL(info) << "Syncing configuration cache"; if (!enabled_config_update) { return; } // Donwload vendor preset bundles - for (const auto &index : index_db) { + for (auto &index : index_db) { if (cancel) { return; } const auto vendor_it = vendors.find(VendorProfile(index.vendor())); @@ -245,17 +246,33 @@ void PresetUpdater::priv::sync_config(const std::set vendors) con // Download a fresh index BOOST_LOG_TRIVIAL(info) << "Downloading index for vendor: " << vendor.name; const auto idx_url = vendor.config_update_url + "/" + INDEX_FILENAME; - const auto idx_path = cache_path / (vendor.id + ".idx"); - if (! get_file(idx_url, idx_path)) { continue; } + const std::string idx_path = (cache_path / (vendor.id + ".idx")).string(); + const std::string idx_path_temp = idx_path + "-update"; + if (!get_file(idx_url, idx_path_temp)) { continue; } if (cancel) { return; } // Load the fresh index up - Index new_index; - new_index.load(idx_path); + { + Index new_index; + try { + new_index.load(idx_path_temp); + } catch (const std::exception &err) { + BOOST_LOG_TRIVIAL(error) << boost::format("Failed loading a downloaded index %1% for vendor %2%: invalid index?") % idx_path_temp % vendor.name; + continue; + } + if (new_index.version() < index.version()) { + BOOST_LOG_TRIVIAL(error) << boost::format("The downloaded index %1% for vendor %2% is older than the active one. Ignoring the downloaded index.") % idx_path_temp % vendor.name; + continue; + } + Slic3r::rename_file(idx_path_temp, idx_path); + index = std::move(new_index); + if (cancel) + return; + } // See if a there's a new version to download - const auto recommended_it = new_index.recommended(); - if (recommended_it == new_index.end()) { + const auto recommended_it = index.recommended(); + if (recommended_it == index.end()) { BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index?") % vendor.name; continue; } diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index 5c4507816..31fe909c4 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -2,10 +2,12 @@ #include #include +#include #include #include #include +#include #include #include "libslic3r/PrintConfig.hpp" @@ -36,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 { @@ -47,6 +59,7 @@ struct PrintHostJobQueue::priv Channel channel_cancels; size_t job_id = 0; int prev_progress = -1; + fs::path source_to_remove; std::thread bg_thread; bool bg_exit = false; @@ -55,10 +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 error_fn(std::string body, std::string error, unsigned http_status); + void remove_source(const fs::path &path); + void remove_source(); void perform_job(PrintHostJob the_job); }; @@ -77,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; } @@ -95,18 +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) { + 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; @@ -121,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::error_fn(std::string body, std::string error, unsigned http_status) +void PrintHostJobQueue::priv::remove_source(const fs::path &path) { - // TODO + 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%` -> `%1%`") - % the_job.upload_data.upload_path - % the_job.printhost->get_host(); + emit_progress(0); // Indicate the upload is starting - const fs::path gcode_path = the_job.upload_data.source_path; - - the_job.printhost->upload(std::move(the_job.upload_data), + 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](std::string body, std::string error, unsigned http_status) { this->error_fn(std::move(body), std::move(error), http_status); } + [this](wxString error) { + emit_error(std::move(error)); + } ); - 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; + if (success) { + emit_progress(100); } } @@ -173,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); +} + } diff --git a/src/slic3r/Utils/PrintHost.hpp b/src/slic3r/Utils/PrintHost.hpp index 52ef38058..d740ea99e 100644 --- a/src/slic3r/Utils/PrintHost.hpp +++ b/src/slic3r/Utils/PrintHost.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -28,15 +29,21 @@ class PrintHost public: virtual ~PrintHost(); + typedef Http::ProgressFn ProgressFn; + typedef std::function ErrorFn; + virtual bool test(wxString &curl_msg) const = 0; virtual wxString get_test_ok_msg () const = 0; virtual wxString get_test_failed_msg (wxString &msg) const = 0; - virtual bool upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const = 0; + virtual bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const = 0; virtual bool has_auto_discovery() const = 0; virtual bool can_test() const = 0; 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; }; @@ -51,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) @@ -62,6 +70,7 @@ struct PrintHostJob { upload_data = std::move(other.upload_data); printhost = std::move(other.printhost); + cancelled = other.cancelled; return *this; }