diff --git a/resources/localization/list.txt b/resources/localization/list.txt index 82f109df4..aa2f938b7 100644 --- a/resources/localization/list.txt +++ b/resources/localization/list.txt @@ -45,6 +45,7 @@ src/slic3r/GUI/WipeTowerDialog.cpp src/slic3r/GUI/wxExtensions.cpp src/slic3r/Utils/Duet.cpp src/slic3r/Utils/OctoPrint.cpp +src/slic3r/Utils/FlashAir.cpp src/slic3r/Utils/PresetUpdater.cpp src/slic3r/Utils/FixModelByWin10.cpp src/libslic3r/Zipper.cpp diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 3db2f1f00..25100b22f 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -951,6 +951,7 @@ ClipperLib::Paths fix_after_inner_offset(const ClipperLib::Path &input, ClipperL ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::vector &deltas, double miter_limit) { assert(contour.size() == deltas.size()); + #ifndef NDEBUG // Verify that the deltas are either all positive, or all negative. bool positive = false; @@ -986,7 +987,10 @@ ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::v double lmin = *std::max_element(deltas.begin(), deltas.end()) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR; double l2min = lmin * lmin; // Minimum angle to consider two edges to be parallel. - double sin_min_parallel = EPSILON + 1. / double(CLIPPER_OFFSET_SCALE); + // Vojtech's estimate. +// const double sin_min_parallel = EPSILON + 1. / double(CLIPPER_OFFSET_SCALE); + // Implementation equal to Clipper. + const double sin_min_parallel = 1.; // Find the last point further from pt by l2min. Vec2d pt = contour.front().cast(); @@ -1012,8 +1016,12 @@ ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::v if (l2 > l2min) break; } - if (j > ilast) + if (j > ilast) { + assert(i <= ilast); + // If the last edge is too short, merge it with the previous edge. + i = ilast; ptnext = contour.front().cast(); + } // Normal to the (ptnext - pt) segment. Vec2d nnext = perp(ptnext - pt).normalized(); @@ -1026,27 +1034,29 @@ ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::v add_offset_point(pt + nprev * delta); add_offset_point(pt); add_offset_point(pt + nnext * delta); - } else if (convex < sin_min_parallel) { - // Nearly parallel. - add_offset_point((nprev.dot(nnext) > 0.) ? (pt + nprev * delta) : pt); } else { - // Convex corner double dot = nprev.dot(nnext); - double r = 1. + dot; - if (r >= miter_limit) - add_offset_point(pt + (nprev + nnext) * (delta / r)); - else { - double dx = std::tan(std::atan2(sin_a, dot) / 4.); - Vec2d newpt1 = pt + (nprev - perp(nprev) * dx) * delta; - Vec2d newpt2 = pt + (nnext + perp(nnext) * dx) * delta; + if (convex < sin_min_parallel && dot > 0.) { + // Nearly parallel. + add_offset_point((nprev.dot(nnext) > 0.) ? (pt + nprev * delta) : pt); + } else { + // Convex corner, possibly extremely sharp if convex < sin_min_parallel. + double r = 1. + dot; + if (r >= miter_limit) + add_offset_point(pt + (nprev + nnext) * (delta / r)); + else { + double dx = std::tan(std::atan2(sin_a, dot) / 4.); + Vec2d newpt1 = pt + (nprev - perp(nprev) * dx) * delta; + Vec2d newpt2 = pt + (nnext + perp(nnext) * dx) * delta; #ifndef NDEBUG - Vec2d vedge = 0.5 * (newpt1 + newpt2) - pt; - double dist_norm = vedge.norm(); - assert(std::abs(dist_norm - delta) < EPSILON); + Vec2d vedge = 0.5 * (newpt1 + newpt2) - pt; + double dist_norm = vedge.norm(); + assert(std::abs(dist_norm - std::abs(delta)) < SCALED_EPSILON); #endif /* NDEBUG */ - add_offset_point(newpt1); - add_offset_point(newpt2); - } + add_offset_point(newpt1); + add_offset_point(newpt2); + } + } } if (i == ilast) diff --git a/src/libslic3r/ElephantFootCompensation.cpp b/src/libslic3r/ElephantFootCompensation.cpp index 5bdeaa954..8b3dda8ae 100644 --- a/src/libslic3r/ElephantFootCompensation.cpp +++ b/src/libslic3r/ElephantFootCompensation.cpp @@ -88,7 +88,7 @@ std::vector contour_distance(const EdgeGrid::Grid &grid, const size_t idx if (std::abs(denom) >= EPSILON) { double t = cross2(dir2, vptpt2) / denom; - assert(t > 0. && t <= 1.); + assert(t > - EPSILON && t < 1. + EPSILON); bool this_valid = true; if (it_contour_and_segment->first == idx_contour) { // The intersected segment originates from the same contour as the starting point. @@ -105,7 +105,7 @@ std::vector contour_distance(const EdgeGrid::Grid &grid, const size_t idx auto it = std::lower_bound(resampled_point_parameters.begin(), resampled_point_parameters.end(), key, lower); assert(it != resampled_point_parameters.end() && it->idx_src == ipt && ! it->interpolated); double t2 = cross2(dir, vptpt2) / denom; - assert(t2 >= 0. && t2 <= 1.); + assert(t2 > - EPSILON && t2 < 1. + EPSILON); if (++ ipt == ipts.size()) param_hi = t2 * dir2.norm(); else diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 33105bff3..e639a6734 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1327,8 +1327,10 @@ void PrintConfigDef::init_fff_params() def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("octoprint"); def->enum_values.push_back("duet"); + def->enum_values.push_back("flashair"); def->enum_labels.push_back("OctoPrint"); def->enum_labels.push_back("Duet"); + def->enum_labels.push_back("FlashAir"); def->mode = comAdvanced; def->set_default_value(new ConfigOptionEnum(htOctoPrint)); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 5c287ba93..4b007fc51 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -30,7 +30,7 @@ enum GCodeFlavor : unsigned char { }; enum PrintHostType { - htOctoPrint, htDuet + htOctoPrint, htDuet, htFlashAir }; enum InfillPattern { @@ -102,6 +102,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum::g if (keys_map.empty()) { keys_map["octoprint"] = htOctoPrint; keys_map["duet"] = htDuet; + keys_map["flashair"] = htFlashAir; } return keys_map; } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index b334b70fc..c4ca46a8c 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1772,7 +1772,8 @@ end: float delta = float(scale_(m_config.xy_size_compensation.value)); //FIXME only apply the compensation if no raft is enabled. float elephant_foot_compensation = 0.f; - if (layer_id == 0) + if (layer_id == 0 && m_config.raft_layers == 0) + // Only enable Elephant foot compensation if printing directly on the print bed. elephant_foot_compensation = float(scale_(m_config.elefant_foot_compensation.value)); if (layer->m_regions.size() == 1) { // Optimized version for a single region layer. @@ -1819,11 +1820,12 @@ end: if (delta < 0.f || elephant_foot_compensation > 0.f) { // Apply the negative XY compensation. Polygons trimming; + static const float eps = float(scale_(m_config.slice_closing_radius.value) * 1.5); if (elephant_foot_compensation > 0.f) { - trimming = to_polygons(Slic3r::elephant_foot_compensation(offset_ex(layer->merged(float(EPSILON)), std::min(delta, 0.f) - float(EPSILON)), + trimming = to_polygons(Slic3r::elephant_foot_compensation(offset_ex(layer->merged(eps), std::min(delta, 0.f) - eps), layer->m_regions.front()->flow(frExternalPerimeter), unscale(elephant_foot_compensation))); } else - trimming = offset(layer->merged(float(EPSILON)), delta - float(EPSILON)); + trimming = offset(layer->merged(float(SCALED_EPSILON)), delta - float(SCALED_EPSILON)); for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) layer->m_regions[region_id]->trim_surfaces(trimming); } diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 17b76e629..84a60da6e 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -144,6 +144,8 @@ set(SLIC3R_GUI_SOURCES Utils/OctoPrint.hpp Utils/Duet.cpp Utils/Duet.hpp + Utils/FlashAir.cpp + Utils/FlashAir.hpp Utils/PrintHost.cpp Utils/PrintHost.hpp Utils/Bonjour.cpp diff --git a/src/slic3r/Utils/FlashAir.cpp b/src/slic3r/Utils/FlashAir.cpp new file mode 100644 index 000000000..2e6a7982d --- /dev/null +++ b/src/slic3r/Utils/FlashAir.cpp @@ -0,0 +1,218 @@ +#include "FlashAir.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/PrintConfig.hpp" +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/MsgDialog.hpp" +#include "Http.hpp" + +namespace fs = boost::filesystem; +namespace pt = boost::property_tree; + +namespace Slic3r { + +FlashAir::FlashAir(DynamicPrintConfig *config) : + host(config->opt_string("print_host")) +{} + +FlashAir::~FlashAir() {} + +const char* FlashAir::get_name() const { return "FlashAir"; } + +bool FlashAir::test(wxString &msg) const +{ + // Since the request is performed synchronously here, + // it is ok to refer to `msg` from within the closure + + const char *name = get_name(); + + bool res = false; + auto url = make_url("command.cgi", "op", "118"); + + BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get upload enabled at: %2%") % name % url; + + auto http = Http::get(std::move(url)); + http.on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting upload enabled: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; + res = false; + msg = format_error(body, error, status); + }) + .on_complete([&, this](std::string body, unsigned) { + BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got upload enabled: %2%") % name % body; + + res = boost::starts_with(body, "1"); + if (! res) { + msg = _(L("Upload not enabled on FlashAir card.")); + } + }) + .perform_sync(); + + return res; +} + +wxString FlashAir::get_test_ok_msg () const +{ + return _(L("Connection to FlashAir works correctly and upload is enabled.")); +} + +wxString FlashAir::get_test_failed_msg (wxString &msg) const +{ + return wxString::Format("%s: %s", _(L("Could not connect to FlashAir")), msg, _(L("Note: FlashAir with firmware 2.00.02 or newer and activated upload function is required."))); +} + +bool FlashAir::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const +{ + const char *name = get_name(); + + 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)) { + error_fn(std::move(test_msg)); + return false; + } + + bool res = false; + + auto urlPrepare = make_url("upload.cgi", "WRITEPROTECT=ON&FTIME", timestamp_str()); + auto urlUpload = make_url("upload.cgi"); + + BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3% / %4%, filename: %5%") + % name + % upload_data.source_path + % urlPrepare + % urlUpload + % upload_filename.string(); + + // set filetime for upload and make card writeprotect to prevent filesystem damage + auto httpPrepare = Http::get(std::move(urlPrepare)); + httpPrepare.on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error prepareing upload: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; + error_fn(format_error(body, error, status)); + res = false; + }) + .on_complete([&, this](std::string body, unsigned) { + BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got prepare result: %2%") % name % body; + res = boost::icontains(body, "SUCCESS"); + if (! res) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name; + error_fn(format_error(body, L("Unknown error occured"), 0)); + } + }) + .perform_sync(); + + if(! res ) { + return res; + } + + // start file upload + auto http = Http::post(std::move(urlUpload)); + http.form_add_file("file", upload_data.source_path.string(), upload_filename.string()) + .on_complete([&](std::string body, unsigned status) { + BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body; + res = boost::icontains(body, "SUCCESS"); + if (! res) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name; + error_fn(format_error(body, L("Unknown error occured"), 0)); + } + }) + .on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % 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) << boost::format("%1%: Upload canceled") % name; + res = false; + } + }) + .perform_sync(); + + return res; +} + +bool FlashAir::has_auto_discovery() const +{ + return false; +} + +bool FlashAir::can_test() const +{ + return true; +} + +bool FlashAir::can_start_print() const +{ + return false; +} + +std::string FlashAir::timestamp_str() const +{ + auto t = std::time(nullptr); + auto tm = *std::localtime(&t); + + const char *name = get_name(); + + unsigned long fattime = ((tm.tm_year - 80) << 25) | + ((tm.tm_mon + 1) << 21) | + (tm.tm_mday << 16) | + (tm.tm_hour << 11) | + (tm.tm_min << 5) | + (tm.tm_sec >> 1); + + return (boost::format("%1$#x") % fattime).str(); +} + +std::string FlashAir::make_url(const std::string &path) const +{ + if (host.find("http://") == 0 || host.find("https://") == 0) { + if (host.back() == '/') { + return (boost::format("%1%%2%") % host % path).str(); + } else { + return (boost::format("%1%/%2%") % host % path).str(); + } + } else { + if (host.back() == '/') { + return (boost::format("http://%1%%2%") % host % path).str(); + } else { + return (boost::format("http://%1%/%2%") % host % path).str(); + } + } +} + +std::string FlashAir::make_url(const std::string &path, const std::string &arg, const std::string &val) const +{ + if (host.find("http://") == 0 || host.find("https://") == 0) { + if (host.back() == '/') { + return (boost::format("%1%%2%?%3%=%4%") % host % path % arg % val).str(); + } else { + return (boost::format("%1%/%2%?%3%=%4%") % host % path % arg % val).str(); + } + } else { + if (host.back() == '/') { + return (boost::format("http://%1%%2%?%3%=%4%") % host % path % arg % val).str(); + } else { + return (boost::format("http://%1%/%2%?%3%=%4%") % host % path % arg % val).str(); + } + } +} + +} diff --git a/src/slic3r/Utils/FlashAir.hpp b/src/slic3r/Utils/FlashAir.hpp new file mode 100644 index 000000000..1499eee5d --- /dev/null +++ b/src/slic3r/Utils/FlashAir.hpp @@ -0,0 +1,44 @@ +#ifndef slic3r_FlashAir_hpp_ +#define slic3r_FlashAir_hpp_ + +#include +#include + +#include "PrintHost.hpp" + + +namespace Slic3r { + + +class DynamicPrintConfig; +class Http; + +class FlashAir : public PrintHost +{ +public: + FlashAir(DynamicPrintConfig *config); + virtual ~FlashAir(); + + virtual const char* get_name() const; + + 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, ProgressFn prorgess_fn, ErrorFn error_fn) const; + virtual bool has_auto_discovery() const; + virtual bool can_test() const; + virtual bool can_start_print() const; + virtual std::string get_host() const { return host; } + +private: + std::string host; + + std::string timestamp_str() const; + std::string make_url(const std::string &path) const; + std::string make_url(const std::string &path, const std::string &arg, const std::string &val) const; +}; + + +} + +#endif diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index ab52b2344..59a929ecc 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -14,6 +14,7 @@ #include "libslic3r/Channel.hpp" #include "OctoPrint.hpp" #include "Duet.hpp" +#include "FlashAir.hpp" #include "../GUI/PrintHostDialogs.hpp" namespace fs = boost::filesystem; @@ -43,6 +44,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config) switch (host_type) { case htOctoPrint: return new OctoPrint(config); case htDuet: return new Duet(config); + case htFlashAir: return new FlashAir(config); default: return nullptr; } } else {